on master help sign in
Simplify code and improve type safety: replace manual JSON parsing with typed deserialization, use JsonPropertyName
commit61c0ab01502e0f3b882828035225b639018f6ac4 authoriko <mail@iko.soy> dateFri 10 Apr 2026, 10:28 (6 months ago) parentf11ecd2
28 files changed+113−93
client/ApiClient.fs+53−51 client/App.fs+4−3 client/bin/Debug/net10.0-ios/iossimulator-arm64/Skrepka.app/Skrepka.dll client/bin/Debug/net10.0-ios/iossimulator-arm64/Skrepka.app/Skrepka.pdb client/bin/Debug/net10.0-ios/iossimulator-arm64/Skrepka.app/_CodeSignature/CodeResources+4−4 client/bin/Debug/net10.0-ios/iossimulator-arm64/Skrepka.dll client/bin/Debug/net10.0-ios/iossimulator-arm64/Skrepka.pdb 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-ios/iossimulator-arm64/Skrepka.AssemblyInfo.fs+1−1 client/obj/Debug/net10.0-ios/iossimulator-arm64/Skrepka.AssemblyInfoInputs.cache+1−1 client/obj/Debug/net10.0-ios/iossimulator-arm64/Skrepka.dll client/obj/Debug/net10.0-ios/iossimulator-arm64/Skrepka.fsproj.FileListAbsolute.txt+22 client/obj/Debug/net10.0-ios/iossimulator-arm64/Skrepka.pdb client/obj/Debug/net10.0-ios/iossimulator-arm64/Skrepka.sourcelink.json+1−1 client/obj/Debug/net10.0-ios/iossimulator-arm64/ref/Skrepka.dll client/obj/Debug/net10.0-ios/iossimulator-arm64/refint/Skrepka.dll 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+22−27
client/ApiClient.fs+53−51
@@ -11,9 +11,26 @@ module ApiClient =
1111 type EventPayload = { EncryptedBlob: string; Timestamp: int64 }
1212 type PollEvent = { Id: string; Payload: EventPayload }
1313
14+ [<CLIMutable>]
1415 type SendResult = { Status: string; MessageId: string }
16+
1517 type PollResponse = { Events: PollEvent list; Cursor: uint64 }
1618
19+ [<CLIMutable>]
20+ type ChallengeResponse = { challenge: string }
21+
22+ [<CLIMutable>]
23+ type VerifyResponse = { token: string }
24+
25+ [<CLIMutable>]
26+ type PollPayloadDto = { encryptedBlob: string; timestamp: JsonElement }
27+
28+ [<CLIMutable>]
29+ type PollEventDto = { id: string; eventType: string; payload: PollPayloadDto }
30+
31+ [<CLIMutable>]
32+ type PollResponseDto = { cursor: uint64; events: PollEventDto array }
33+
1734 /// Awaits a Task without converting TaskCanceledException to F# async
1835 /// cancellation (which bypasses try...with). Re-raises it as a regular exception.
1936 let private awaitTask (t: Task<'T>) : Async<'T> =
@@ -24,12 +41,19 @@ module ApiClient =
2441 else ok t.Result)
2542 |> ignore)
2643
44+ let private jsonOpts = JsonSerializerOptions(PropertyNameCaseInsensitive = true)
45+
2746 let private client =
2847 let c = new HttpClient()
2948 c.Timeout <- TimeSpan.FromSeconds(60.)
3049 c
3150
32− let private postJsonWith (httpClient: HttpClient) (url: string) (body: string) (token: string option) =
51+ let private pollClient =
52+ let c = new HttpClient()
53+ c.Timeout <- TimeSpan.FromSeconds(120.)
54+ c
55+
56+ let private sendRequest (httpClient: HttpClient) (url: string) (body: string) (token: string option) =
3357 async {
3458 let request = new HttpRequestMessage(HttpMethod.Post, url)
3559 request.Content <- new StringContent(body, Encoding.UTF8, "application/json")
@@ -39,82 +63,60 @@ module ApiClient =
3963
4064 let! response = httpClient.SendAsync(request) |> awaitTask
4165 let! text = response.Content.ReadAsStringAsync() |> awaitTask
42− return JsonDocument.Parse(text)
43− }
66+ use doc = JsonDocument.Parse(text)
4467
45− let private postJson url body token = postJsonWith client url body token
46−
47− let private expectObject (doc: JsonDocument) (endpoint: string) =
48− let root = doc.RootElement
49−
50− if root.ValueKind <> JsonValueKind.Object then
51− failwith $"{endpoint}: unexpected response: {root.GetRawText()}"
68+ match doc.RootElement.TryGetProperty("error") with
69+ | true, err -> return failwith $"{url}: {err.GetString()}"
70+ | _ -> return text
71+ }
5272
53− match root.TryGetProperty("error") with
54− | true, err -> failwith $"{endpoint}: server error: {err.GetString()}"
55− | _ -> root
73+ let private postJson<'T> httpClient url body token =
74+ async {
75+ let! text = sendRequest httpClient url body token
76+ return JsonSerializer.Deserialize<'T>(text, jsonOpts)
77+ }
5678
5779 let requestChallenge (serverUrl: string) (pubkeyHex: string) =
5880 async {
5981 let body = JsonSerializer.Serialize({| pubkey = pubkeyHex |})
60− let! doc = postJson $"{serverUrl}/auth/challenge" body None
61− let root = expectObject doc "/auth/challenge"
62− return root.GetProperty("challenge").GetString()
82+ let! response = postJson<ChallengeResponse> client $"{serverUrl}/auth/challenge" body None
83+ return response.challenge
6384 }
6485
6586 let verify (serverUrl: string) (pubkeyHex: string) (challenge: string) (signatureHex: string) =
6687 async {
6788 let body = JsonSerializer.Serialize({| pubkey = pubkeyHex; challenge = challenge; signature = signatureHex |})
68−
69− let! doc = postJson $"{serverUrl}/auth/verify" body None
70− let root = expectObject doc "/auth/verify"
71− let token = root.GetProperty("token").GetString()
72− if String.IsNullOrEmpty(token) then failwith "Authentication rejected by server"
73− return token
89+ let! response = postJson<VerifyResponse> client $"{serverUrl}/auth/verify" body None
90+ if String.IsNullOrEmpty(response.token) then failwith "Authentication rejected by server"
91+ return response.token
7492 }
7593
7694 let sendMessage (serverUrl: string) (token: string) (toHex: string) (blobHex: string) (timestamp: int64) =
7795 async {
7896 let body = JsonSerializer.Serialize({| ``to`` = toHex; encryptedBlob = blobHex; timestamp = timestamp |})
79−
80− let! doc = postJson $"{serverUrl}/messages" body (Some token)
81− let root = expectObject doc "/messages"
82− return
83− { Status = root.GetProperty("status").GetString()
84− MessageId = root.GetProperty("messageId").GetString() }
97+ return! postJson<SendResult> client $"{serverUrl}/messages" body (Some token)
8598 }
8699
87− let private pollClient =
88− let c = new HttpClient()
89− c.Timeout <- TimeSpan.FromSeconds(120.)
90− c
91−
92100 let poll (serverUrl: string) (token: string) (cursor: uint64) =
93101 async {
94102 let body = JsonSerializer.Serialize({| cursor = cursor; timeout = 30000 |})
95− let! doc = postJsonWith pollClient $"{serverUrl}/poll" body (Some token)
96− let root = expectObject doc "/poll"
97− let cursor = root.GetProperty("cursor").GetUInt64()
98−
99− let events = root.GetProperty("events")
103+ let! response = postJson<PollResponseDto> pollClient $"{serverUrl}/poll" body (Some token)
100104
101105 let events =
102− [ for e in events.EnumerateArray() do
103− if e.ValueKind = JsonValueKind.Object
104− && e.GetProperty("eventType").GetString() = "message" then
105− let p = e.GetProperty("payload")
106− let ts = p.GetProperty("timestamp")
107− yield
108− { Id = e.GetProperty("id").GetString()
109− Payload =
110− { EncryptedBlob = p.GetProperty("encryptedBlob").GetString()
111− Timestamp = if ts.ValueKind = JsonValueKind.String then Int64.Parse(ts.GetString()) else ts.GetInt64() } } ]
112−
113− return { Events = events; Cursor = cursor }
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 }
114116 }
115117
116118 let ackMessages (serverUrl: string) (token: string) (messageIds: string list) =
117119 async {
118120 let body = JsonSerializer.Serialize({| messageIds = messageIds |})
119− do! postJson $"{serverUrl}/messages/ack" body (Some token) |> Async.Ignore
121+ do! sendRequest client $"{serverUrl}/messages/ack" body (Some token) |> Async.Ignore
120122 }
client/App.fs+4−3
@@ -2,6 +2,7 @@ namespace Skrepka
22
33open System
44open System.Text.Json
5+open System.Text.Json.Serialization
56open Fabulous
67open Fabulous.Maui
78open System.Collections.Generic
@@ -23,7 +24,7 @@ module App =
2324
2425 [<CLIMutable>]
2526 type TextEnvelope =
26− { ``type``: string
27+ { [<JsonPropertyName("type")>] Type: string
2728 id: string
2829 timestamp: int64
2930 body: string }
@@ -311,7 +312,7 @@ module App =
311312 | Some(plaintext, senderHex) ->
312313 let envelope = JsonSerializer.Deserialize<TextEnvelope>(plaintext)
313314
314− if envelope.``type`` = "text" then
315+ if envelope.Type = "text" then
315316 Ok(senderHex,
316317 { Id = envelope.id
317318 Body = envelope.body
@@ -346,7 +347,7 @@ module App =
346347 let ts = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
347348
348349 let payload =
349− JsonSerializer.Serialize({ ``type`` = "text"; id = messageId; timestamp = ts; body = text }: TextEnvelope)
350+ JsonSerializer.Serialize({ Type = "text"; id = messageId; timestamp = ts; body = text }: TextEnvelope)
350351
351352 let blob = Crypto.encrypt session.Identity.PrivKey recipPub payload
352353 let blobHex = Crypto.toHex blob
client/bin/Debug/net10.0-ios/iossimulator-arm64/Skrepka.app/Skrepka.dll

Binary file not shown.

client/bin/Debug/net10.0-ios/iossimulator-arm64/Skrepka.app/Skrepka.pdb

Binary file not shown.

client/bin/Debug/net10.0-ios/iossimulator-arm64/Skrepka.app/_CodeSignature/CodeResources+4−4
@@ -162,11 +162,11 @@
162162 </data>
163163 <key>Skrepka.dll</key>
164164 <data>
165− HO+rGAQB+KP+O6GLdnrpg4gXNTo=
165+ Fx5eEqogOKsTrWA3kWp16d3fSyU=
166166 </data>
167167 <key>Skrepka.pdb</key>
168168 <data>
169− gVY2miH2VNwTxjVewvuMbzDx/GU=
169+ HqhmdetfGb7BH5l9fuwhGdebTOE=
170170 </data>
171171 <key>Sodium.Core.dll</key>
172172 <data>
@@ -1382,14 +1382,14 @@
13821382 <dict>
13831383 <key>hash2</key>
13841384 <data>
1385− D1zjN3wi4JGWYPuOGUv3OatZGfdLQhYyE2a3FQRO3Sk=
1385+ FjsECHINikzgAyKIKVfKchjBJ13FDPE/H9z1BXBRz0w=
13861386 </data>
13871387 </dict>
13881388 <key>Skrepka.pdb</key>
13891389 <dict>
13901390 <key>hash2</key>
13911391 <data>
1392− Qdm9JVWHZq1+ocF6a6hRUFeaSuFiylZp+ykwgIfTQ3A=
1392+ 3GjYcjVCL6cSFSxxBQY9gaQpUEE77fX5bueUq6AVhZQ=
13931393 </data>
13941394 </dict>
13951395 <key>Sodium.Core.dll</key>
client/bin/Debug/net10.0-ios/iossimulator-arm64/Skrepka.dll

Binary file not shown.

client/bin/Debug/net10.0-ios/iossimulator-arm64/Skrepka.pdb

Binary file not shown.

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− iGYj1YzZ1prtD0NbQ3DXzBIxx8GfZCXBBkeG+NhAlAg=
243+ Tb9gTNQn0vC6xYnphz/SPqKOypsS1ii7ovZ7S6s4QoY=
244244 </data>
245245 </dict>
246246 <key>MonoBundle/Skrepka.pdb</key>
247247 <dict>
248248 <key>hash2</key>
249249 <data>
250− gc3cHrHWiW0qjPSnbfHqE/s8LkMQGikr2nzPGAaHEgE=
250+ k0t0cbTVWz/CfZ4rlDzYjSX4lniTHLZRvz2UrUCSZdM=
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-ios/iossimulator-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+79a607dd3a4773a3bae1ef20240a704c153f91da")>]
13+[<assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0+f11ecd2201e52dce49ef2d913500531961808e00")>]
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-ios/iossimulator-arm64/Skrepka.AssemblyInfoInputs.cache+1−1
@@ -1 +1 @@
1−2cd69f5b98b69e1a72e3f4bd87ef1f300a712fcc06d03dda524b6c00fdfe5200
1+07f32a66419e75da06f8eeb4cc8f91961c7616db00e976ebd52190a10f03b26f
client/obj/Debug/net10.0-ios/iossimulator-arm64/Skrepka.dll

Binary file not shown.

client/obj/Debug/net10.0-ios/iossimulator-arm64/Skrepka.fsproj.FileListAbsolute.txt+22
@@ -333,3 +333,25 @@
333333/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/Skrepka.pdb
334334/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/Skrepka.genruntimeconfig.cache
335335/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/ref/Skrepka.dll
336+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Fabulous.stamp
337+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Fabulous.MauiControls.stamp
338+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/FSharp.Core.stamp
339+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Extensions.Configuration.Abstractions.stamp
340+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Extensions.Configuration.stamp
341+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Extensions.DependencyInjection.Abstractions.stamp
342+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Extensions.DependencyInjection.stamp
343+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Extensions.Diagnostics.Abstractions.stamp
344+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Extensions.FileProviders.Abstractions.stamp
345+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Extensions.Hosting.Abstractions.stamp
346+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Extensions.Logging.Abstractions.stamp
347+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Extensions.Logging.stamp
348+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Extensions.Options.stamp
349+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Extensions.Primitives.stamp
350+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Maui.Controls.Compatibility.stamp
351+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Maui.Controls.stamp
352+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Maui.Controls.Xaml.stamp
353+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Maui.stamp
354+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Maui.Essentials.stamp
355+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Maui.Graphics.stamp
356+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Sodium.Core.stamp
357+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/unpack/Microsoft.Maui.Essentials/BundleResource/xml/microsoft_maui_essentials_fileprovider_file_paths.xml
client/obj/Debug/net10.0-ios/iossimulator-arm64/Skrepka.pdb

Binary file not shown.

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

Binary file not shown.

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

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+79a607dd3a4773a3bae1ef20240a704c153f91da")>]
13+[<assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0+f11ecd2201e52dce49ef2d913500531961808e00")>]
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−20452108ea7920252e262f31e3d85af043c1d46fd7b19b9a54754e30a319f325
1+90103b2909e2d6c308fd0241aa80b308b414d7047eeca8cef6ba5f4642cdf15a
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/79a607dd3a4773a3bae1ef20240a704c153f91da/*"}}
1+{"documents":{"/Users/iko/Developer/skrepka/*":"https://raw.githubusercontent.com/ilyakooo0/skrepka/f11ecd2201e52dce49ef2d913500531961808e00/*"}}
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+22−27
@@ -626,7 +626,7 @@ retryPendingForwards = \pending -> do
626626 retryForwardsForServer (do x <- pending; where x.toServer == server; yield x) maxForwardRetriesPerServer)
627627 yield {}
628628
629−sendBatchedAcksToServer : [{messageId: MessageIdHex, toKey: PubkeyHex, originServer: Text, | a}] -> Text -> IO {network} {}
629+sendBatchedAcksToServer : [{messageId: MessageIdHex, toKey: PubkeyHex, originServer: ServerName, | a}] -> ServerName -> IO {network} {}
630630sendBatchedAcksToServer = \serverAcks server ->
631631 forEachBatch serverAcks maxMessageIds (\batch -> do
632632 result <- fetch (federationScheme ++ server) (FederationAck {
@@ -643,7 +643,7 @@ sendBatchedAcksToServer = \serverAcks server ->
643643 yield a
644644 Err _ -> yield {})
645645
646−sendAcksAndClean : [{messageId: MessageIdHex, toKey: PubkeyHex, originServer: Text, | a}] -> [Text] -> IO {network} {}
646+sendAcksAndClean : [{messageId: MessageIdHex, toKey: PubkeyHex, originServer: ServerName, | a}] -> [ServerName] -> IO {network} {}
647647sendAcksAndClean = \pendingAcks servers ->
648648 sendToEach servers (\server ->
649649 sendBatchedAcksToServer (do a <- pendingAcks; where a.originServer == server; yield a) server)
@@ -668,15 +668,15 @@ pollOnce = \token cursor -> do
668668 t <- now
669669 atomic do
670670 sessions <- *sessions
671− seqCounter <- *seqCounter
672671 let auth = authenticateSession sessions token t
673− let cursorCeil = if seqCounter > t then seqCounter else t
674− let clampedCursor = if cursor < 0 then 0 else if cursor > cursorCeil then cursorCeil else cursor
675672 case auth of
676673 Unauthorized {} ->
677674 yield {events: [], cursor: -1, authorized: False {}}
678675 Authorized {pubkey} -> do
676+ seqCounter <- *seqCounter
679677 messages <- *messages
678+ let cursorCeil = if seqCounter > t then seqCounter else t
679+ let clampedCursor = if cursor < 0 then 0 else if cursor > cursorCeil then cursorCeil else cursor
680680 let matchingMsgs = do
681681 m <- messages
682682 where m.toKey == pubkey
@@ -808,7 +808,7 @@ serve = \req -> case req of
808808 let auth = authenticateSession sessions (extractToken authorization) t
809809 case auth of
810810 Unauthorized {} ->
811− yield {status: "unauthorized", shouldInsert: False {}, remoteServers: [], fromKey: "", expiredLocal: pr.expiredLocal, peerServers: pr.peerServers, forwardsAtCapacity: False {}}
811+ yield {status: "unauthorized", shouldInsert: False {}, remoteServers: [], fromKey: "", expiredLocal: pr.expiredLocal, peerServers: pr.peerServers}
812812 Authorized {pubkey: fromKey} -> do
813813 messages <- *messages
814814 presence <- *presence
@@ -825,17 +825,15 @@ serve = \req -> case req of
825825 let localOnline = any toPresence (\p -> p.server == serverHostN)
826826 let remoteServers = do p <- toPresence; where not (p.server == serverHostN); yield p.server
827827 forwards <- *forwards
828− let forwardsAtCapacity = count forwards >= maxForwardsEntries
828+ let forwardServers = takeN remoteServers (maxForwardsEntries - count forwards)
829829 let status = if not shouldInsert then "rejected"
830830 else if localOnline then "delivered"
831− else if count remoteServers > 0 && federationEnabled && not forwardsAtCapacity then "federated"
831+ else if federationEnabled && count forwardServers > 0 then "federated"
832832 else "queued"
833− let forwardSlots = maxForwardsEntries - count forwards
834− let forwardServers = takeN remoteServers forwardSlots
835− set *forwards = if shouldInsert && not forwardsAtCapacity && count forwardServers > 0 && federationEnabled
833+ set *forwards = if shouldInsert && federationEnabled && count forwardServers > 0
836834 then union forwards (do s <- forwardServers; yield {messageId: msgId, toServer: s, createdAt: t, toKey: to, fromKey: fromKey, encryptedBlob: encryptedBlob, timestamp: clampedTs})
837835 else forwards
838− yield {status: status, shouldInsert: shouldInsert, remoteServers: remoteServers, fromKey: fromKey, expiredLocal: pr.expiredLocal, peerServers: pr.peerServers, forwardsAtCapacity: forwardsAtCapacity}
836+ yield {status: status, shouldInsert: shouldInsert, remoteServers: remoteServers, fromKey: fromKey, expiredLocal: pr.expiredLocal, peerServers: pr.peerServers}
839837 fork (do
840838 when (count result.remoteServers > 0 && result.shouldInsert && federationEnabled)
841839 (sendToEach result.remoteServers (\server -> do
@@ -846,7 +844,7 @@ serve = \req -> case req of
846844 case fwdResult of
847845 Ok _ -> removeForward msgId server
848846 Err _ -> yield {}))
849− when (result.forwardsAtCapacity && count result.remoteServers > 0 && result.shouldInsert)
847+ when (result.status == "queued" && count result.remoteServers > 0 && result.shouldInsert && federationEnabled)
850848 (println ("sendMessage: forwards at capacity (" ++ show maxForwardsEntries ++ "), skipping forward tracking"))
851849 broadcastOfflineGossip t result.peerServers result.expiredLocal)
852850 yield (respond {status: result.status, messageId: if result.shouldInsert then msgId else ""})
@@ -953,9 +951,14 @@ serve = \req -> case req of
953951 e <- onlineEntries
954952 where none offlineEntries (\o -> o.server == fromServer && samePubkeyServer o e && o.timestamp > e.timestamp)
955953 yield e
956− let affectedKeys = do
957− e <- allValidEntries
958− yield {pubkey: e.pubkey, server: e.server}
954+ let filteredOffline = do
955+ e <- offlineEntries
956+ where none onlineEntries (\o -> samePubkeyServer o e && o.timestamp > e.timestamp)
957+ yield e
958+ let trustedOffline = do
959+ e <- offlineEntries
960+ where e.server == fromServer
961+ yield e
959962 let deduped = mergeByPubkeyServer [] filteredOnline
960963 gossipResult <- atomic do
961964 pr <- maybePrune t
@@ -964,19 +967,15 @@ serve = \req -> case req of
964967 e <- deduped
965968 where e.server == fromServer || any presence (\p -> p.server == e.server && p.expiresAt > t)
966969 yield e
967− let trustedOffline = do
968− e <- offlineEntries
969− where e.server == fromServer
970− yield e
971970 let appliedOnline = do
972971 e <- trustedDeduped
973972 where none presence (\p -> samePubkeyServer p e && p.expiresAt >= e.expiresAt)
974973 yield {pubkey: e.pubkey, server: e.server}
975974 let merged = mergeByPubkeyServer presence trustedDeduped
976975 let offlineOnlyKeys = do
977− k <- affectedKeys
978− where none trustedDeduped (\d -> samePubkeyServer d k)
979− yield k
976+ e <- allValidEntries
977+ where none trustedDeduped (\d -> samePubkeyServer d e)
978+ yield e
980979 let appliedOffline = do
981980 k <- offlineOnlyKeys
982981 where any presence (\p -> samePubkeyServer p k && any trustedOffline (\o -> samePubkeyServer o k && (if o.timestamp > t then t else o.timestamp) > p.timestamp))
@@ -1000,10 +999,6 @@ serve = \req -> case req of
1000999 set *presence = finalPresence
10011000 let gossipPeerServers = collectPeerServers finalPresence (\p -> p.server)
10021001 yield {appliedKeys: union capResult.appliedOnline appliedOffline, expiredLocal: pr.expiredLocal, peerServers: pr.peerServers, gossipPeerServers: gossipPeerServers, withinCapacity: withinCapacity}
1003− let filteredOffline = do
1004− e <- offlineEntries
1005− where none onlineEntries (\o -> samePubkeyServer o e && o.timestamp > e.timestamp)
1006− yield e
10071002 let effectiveEvents = do
10081003 e <- union filteredOnline filteredOffline
10091004 where any gossipResult.appliedKeys (\a -> samePubkeyServer a e)