on master help sign in
Simplify code and improve type safety: combine Identity and ConnState into AuthState, type SendResult.Status as MessageStatus, use Option.ofObj, handle message rejection, skip spurious connect
commit5c34fab63923cdb1b780a7e5a5ce5fae72adf78e authoriko <mail@iko.soy> dateFri 10 Apr 2026, 12:37 (6 months ago) parent01f5f20
29 files changed+177−121
client/ApiClient.fs+23−1 client/App.fs+52−41 client/Store.fs+10−10 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+23 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+57−57
client/ApiClient.fs+23−1
@@ -15,8 +15,10 @@ module ApiClient =
1515 [<CLIMutable>]
1616 type PollEvent = { Id: string; EventType: string; Payload: EventPayload }
1717
18+ type MessageStatus = Delivered | Federated | Queued | Rejected | Unauthorized
19+
1820 [<CLIMutable>]
19− type SendResult = { Status: string; MessageId: string }
21+ type SendResult = { Status: MessageStatus; MessageId: string }
2022
2123 [<CLIMutable>]
2224 type PollResponse = { Cursor: int64; Events: PollEvent array }
@@ -45,9 +47,29 @@ module ApiClient =
4547 | _ -> reader.GetInt64()
4648 override _.Write(writer, value, _) = writer.WriteNumberValue(value)
4749
50+ type private MessageStatusConverter() =
51+ inherit JsonConverter<MessageStatus>()
52+ override _.Read(reader, _, _) =
53+ match reader.GetString() with
54+ | "delivered" -> Delivered
55+ | "federated" -> Federated
56+ | "queued" -> Queued
57+ | "rejected" -> Rejected
58+ | "unauthorized" -> Unauthorized
59+ | s -> failwith $"Unknown message status: {s}"
60+ override _.Write(writer, value, _) =
61+ writer.WriteStringValue(
62+ match value with
63+ | Delivered -> "delivered"
64+ | Federated -> "federated"
65+ | Queued -> "queued"
66+ | Rejected -> "rejected"
67+ | Unauthorized -> "unauthorized")
68+
4869 let private jsonOpts =
4970 let opts = JsonSerializerOptions(PropertyNameCaseInsensitive = true)
5071 opts.Converters.Add(FlexibleInt64Converter())
72+ opts.Converters.Add(MessageStatusConverter())
5173 opts
5274
5375 let private client =
client/App.fs+52−41
@@ -32,18 +32,21 @@ module App =
3232
3333 type Session = { Url: string; Token: string; Identity: Identity }
3434
35− type ConnState =
35+ type ConnStatus =
3636 | Offline
3737 | Connecting
38− | Online of Session
38+ | Online of token: string
39+
40+ type AuthState =
41+ | NoIdentity
42+ | Identified of identity: Identity * conn: ConnStatus
3943
4044 // ── Model ──
4145
4246 type Model =
4347 { Page: Page
44− Identity: Identity option
48+ Auth: AuthState
4549 ServerUrl: string
46− Conn: ConnState
4750 Contacts: Contact list
4851 PollStatus: string
4952 Messages: Map<string, ChatMessage list>
@@ -114,13 +117,20 @@ module App =
114117 contacts @ [ contact ]
115118
116119 let private pubKeyHex model =
117− model.Identity |> Option.map _.PubKeyHex |> Option.defaultValue ""
120+ match model.Auth with
121+ | Identified(id, _) -> id.PubKeyHex
122+ | NoIdentity -> ""
118123
119124 let private trySession model =
120− match model.Conn with
121− | Online session -> Some session
125+ match model.Auth with
126+ | Identified(id, Online token) -> Some { Url = model.ServerUrl; Token = token; Identity = id }
122127 | _ -> None
123128
129+ let private setConn status model =
130+ match model.Auth with
131+ | Identified(id, _) -> { model with Auth = Identified(id, status) }
132+ | NoIdentity -> model
133+
124134 let private saveCmd model =
125135 [ CmdSaveData { Contacts = model.Contacts; Messages = model.Messages; ServerUrl = model.ServerUrl; PollCursor = model.PollCursor } ]
126136
@@ -128,9 +138,8 @@ module App =
128138
129139 let init () =
130140 { Page = Setup
131− Identity = None
141+ Auth = NoIdentity
132142 ServerUrl = "http://localhost:8080"
133− Conn = Offline
134143 PollStatus = ""
135144 Contacts = []
136145 Messages = Map.empty
@@ -147,28 +156,28 @@ module App =
147156 | GenIdentity -> model, [ CmdGenIdentity ]
148157
149158 | IdentityReady identity ->
150− { model with Identity = Some identity; Page = Settings },
159+ { model with Auth = Identified(identity, Offline); Page = Settings },
151160 [ CmdSaveIdentity identity ]
152161
153162 | SetServerUrl url -> { model with ServerUrl = url }, []
154163
155164 | DoConnect ->
156− match model.Identity with
157− | Some id ->
158− { model with Conn = Connecting }, [ CmdConnect(model.ServerUrl, id) ]
159− | None -> { model with Error = Some "No identity generated" }, []
165+ match model.Auth with
166+ | Identified(id, _) ->
167+ model |> setConn Connecting, [ CmdConnect(model.ServerUrl, id) ]
168+ | NoIdentity -> { model with Error = Some "No identity generated" }, []
160169
161170 | AuthOk token ->
162− match model.Identity, model.Conn with
163− | Some id, Connecting ->
171+ match model.Auth with
172+ | Identified(id, Connecting) ->
164173 let session = { Url = model.ServerUrl; Token = token; Identity = id }
165− { model with Conn = Online session; Page = Conversations; Error = None },
174+ { model with Auth = Identified(id, Online token); Page = Conversations; Error = None },
166175 [ CmdPoll(session, model.PollCursor) ]
167176 | _ -> model, []
168177
169− | AuthErr err -> { model with Conn = Offline; Error = Some err }, []
178+ | AuthErr err -> { setConn Offline model with Error = Some err }, []
170179
171− | DoDisconnect -> { model with Conn = Offline }, []
180+ | DoDisconnect -> model |> setConn Offline, []
172181
173182 | SetCompose text ->
174183 match model.Page with
@@ -193,14 +202,18 @@ module App =
193202 model', CmdSend(session, pk, compose, id) :: saveCmd model'
194203 | _ -> model, []
195204
196− | Sent(Ok _) -> model, []
205+ | Sent(Ok result) ->
206+ match result.Status with
207+ | ApiClient.Rejected -> { model with Error = Some "Message rejected by server" }, []
208+ | ApiClient.Unauthorized -> { model with Error = Some "Not authorized to deliver message" }, []
209+ | _ -> model, []
197210 | Sent(Error err) -> { model with Error = Some $"Send failed: {err}" }, []
198211
199212 | PollResult(incoming, status, newCursor) ->
200213 let model' =
201214 incoming
202215 |> List.fold
203− (fun m (fromPubkey, chatMsg) ->
216+ (fun (m: Model) (fromPubkey, chatMsg) ->
204217 { m with
205218 Contacts = m.Contacts |> addContactIfNew { Pubkey = fromPubkey; Nickname = truncKey fromPubkey }
206219 Messages = m.Messages |> appendMessage fromPubkey chatMsg })
@@ -237,28 +250,26 @@ module App =
237250 | _ -> model, []
238251
239252 | CopyPubKey ->
240− match model.Identity with
241− | Some id -> model, [ CmdCopyToClipboard id.PubKeyHex ]
242− | None -> model, []
253+ match model.Auth with
254+ | Identified(id, _) -> model, [ CmdCopyToClipboard id.PubKeyHex ]
255+ | NoIdentity -> model, []
243256
244257 | DismissError -> { model with Error = None }, []
245258
246259 | StateLoaded(identity, dataOpt) ->
247− let model' =
248− match dataOpt with
249− | Some data ->
260+ match dataOpt with
261+ | Some data ->
262+ let model' =
250263 { model with
251− Identity = Some identity
252− Conn = Connecting
264+ Auth = Identified(identity, Connecting)
253265 Contacts = data.Contacts
254266 Messages = data.Messages
255267 ServerUrl = if data.ServerUrl <> "" then data.ServerUrl else model.ServerUrl
256268 PollCursor = data.PollCursor
257269 Page = Conversations }
258− | None ->
259− { model with Identity = Some identity; Conn = Connecting; Page = Settings }
260−
261− model', [ CmdConnect(model'.ServerUrl, identity) ]
270+ model', [ CmdConnect(model'.ServerUrl, identity) ]
271+ | None ->
272+ { model with Auth = Identified(identity, Offline); Page = Settings }, []
262273
263274 // ── mapCmd ──
264275
@@ -424,10 +435,10 @@ module App =
424435 Button("Dismiss", DismissError)
425436 | None -> ()
426437
427− match model.Conn with
428− | Offline -> Button("Connect", DoConnect)
429− | Connecting -> Label("Connecting...").centerTextHorizontal()
430− | Online _ ->
438+ match model.Auth with
439+ | Identified(_, Offline) | NoIdentity -> Button("Connect", DoConnect)
440+ | Identified(_, Connecting) -> Label("Connecting...").centerTextHorizontal()
441+ | Identified(_, Online _) ->
431442 Button("Disconnect", DoDisconnect)
432443 Button("Go to Conversations", Nav Conversations)
433444 })
@@ -437,10 +448,10 @@ module App =
437448
438449 let private viewConversations model =
439450 let connStr =
440− match model.Conn with
441− | Offline -> "OFFLINE"
442− | Connecting -> "CONNECTING"
443− | Online _ -> "ONLINE"
451+ match model.Auth with
452+ | NoIdentity | Identified(_, Offline) -> "OFFLINE"
453+ | Identified(_, Connecting) -> "CONNECTING"
454+ | Identified(_, Online _) -> "ONLINE"
444455
445456 let contactNames =
446457 model.Contacts
client/Store.fs+10−10
@@ -51,13 +51,13 @@ module Store =
5151 SecureStorage.Default.GetAsync("identity_pubkey")
5252 |> Async.AwaitTask
5353
54− if isNull privKeyB64 || isNull pubKeyHex then
55− return None
56− else
57− return
58− Some
59− { Crypto.Identity.PrivKey = Convert.FromBase64String privKeyB64
60− Crypto.Identity.PubKeyHex = pubKeyHex }
54+ return
55+ Option.map2
56+ (fun pk pub ->
57+ { Crypto.Identity.PrivKey = Convert.FromBase64String pk
58+ Crypto.Identity.PubKeyHex = pub })
59+ (Option.ofObj privKeyB64)
60+ (Option.ofObj pubKeyHex)
6161 }
6262
6363 let saveIdentity (identity: Crypto.Identity) =
@@ -71,9 +71,9 @@ module Store =
7171 use db = openDb ()
7272 let settings = db.GetCollection<SettingsDoc>("settings").FindById(BsonValue "settings")
7373
74− if box settings |> isNull then
75− None
76− else
74+ match box settings with
75+ | null -> None
76+ | _ ->
7777 let contacts = db.GetCollection<Contact>("contacts").FindAll() |> Seq.toList
7878
7979 let messages =
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
@@ -166,11 +166,11 @@
166166 </data>
167167 <key>Skrepka.dll</key>
168168 <data>
169− 1YPTBw0a3jfRaHp/FqqEqcamsFo=
169+ 5V7ZArTTdWWdtF2EQw4DfWAWqsI=
170170 </data>
171171 <key>Skrepka.pdb</key>
172172 <data>
173− 318b9V+eeWqqTJ1X1o5LHMWnywY=
173+ SsPgpZXl35VDuHCOYCegZuLqIVg=
174174 </data>
175175 <key>Sodium.Core.dll</key>
176176 <data>
@@ -1393,14 +1393,14 @@
13931393 <dict>
13941394 <key>hash2</key>
13951395 <data>
1396− w1+88siJc/avY1PQmPGLGB46y4akJb8sDoczgEVI4fM=
1396+ EjrAHE9wqEvTYF+Siheu21r0UjeHDIZTT38w82FXRYU=
13971397 </data>
13981398 </dict>
13991399 <key>Skrepka.pdb</key>
14001400 <dict>
14011401 <key>hash2</key>
14021402 <data>
1403− gaPNj44wAMOFLNjNCIJfaKHiU9IgJDuOQItWdMdbM2Y=
1403+ cqhfrvM9zLFFTemz+KoZd7JckLQGVacy+ANck7D2x18=
14041404 </data>
14051405 </dict>
14061406 <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
@@ -251,14 +251,14 @@
251251 <dict>
252252 <key>hash2</key>
253253 <data>
254− ACzMr65iZCRxNkmPNzA6fyn/yv2+20NTfHo+2J3hOVo=
254+ fVOogHexeXD8oGEEo57l14SmUBycR/4qC7AH6BhBs7k=
255255 </data>
256256 </dict>
257257 <key>MonoBundle/Skrepka.pdb</key>
258258 <dict>
259259 <key>hash2</key>
260260 <data>
261− 7pMDhAJJCLuEC9ASqtHFdvdW1xJFyw0foQ6ySH91mrU=
261+ ZOxyEQ1fCheg8RSrL3C6orb8q4+ouFNQ6lWNgWyrhPo=
262262 </data>
263263 </dict>
264264 <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+f4311e3ec7c47d3491f648d03dad91cf7a73b5d9")>]
13+[<assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0+01f5f207fb604677f732e61a361f792f91a2b5c4")>]
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−1e95aacb0126fb0fa0c97c9af194457ae6f1585fbb05c5244efbdd807cb04e1c
1+e0e8e8e8cd6c9e5da1d472b9aaaa2bf913ad3f59c98c11e0e07666057c474551
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+23
@@ -334,3 +334,26 @@
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
336336/Users/iko/Developer/skrepka/client/bin/Debug/net10.0-ios/iossimulator-arm64/LiteDB.dll
337+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Fabulous.stamp
338+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Fabulous.MauiControls.stamp
339+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/FSharp.Core.stamp
340+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/LiteDB.stamp
341+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Extensions.Configuration.Abstractions.stamp
342+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Extensions.Configuration.stamp
343+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Extensions.DependencyInjection.Abstractions.stamp
344+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Extensions.DependencyInjection.stamp
345+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Extensions.Diagnostics.Abstractions.stamp
346+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Extensions.FileProviders.Abstractions.stamp
347+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Extensions.Hosting.Abstractions.stamp
348+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Extensions.Logging.Abstractions.stamp
349+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Extensions.Logging.stamp
350+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Extensions.Options.stamp
351+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Extensions.Primitives.stamp
352+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Maui.Controls.Compatibility.stamp
353+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Maui.Controls.stamp
354+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Maui.Controls.Xaml.stamp
355+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Maui.stamp
356+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Maui.Essentials.stamp
357+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Microsoft.Maui.Graphics.stamp
358+/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/Sodium.Core.stamp
359+/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/f4311e3ec7c47d3491f648d03dad91cf7a73b5d9/*"}}
1+{"documents":{"/Users/iko/Developer/skrepka/*":"https://raw.githubusercontent.com/ilyakooo0/skrepka/01f5f207fb604677f732e61a361f792f91a2b5c4/*"}}
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+f4311e3ec7c47d3491f648d03dad91cf7a73b5d9")>]
13+[<assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0+01f5f207fb604677f732e61a361f792f91a2b5c4")>]
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−0e18d76dae776b5ef2d17435d35ec648e83dd873ca4b5da4371eead24ce9a533
1+b4711a8c2112127a9d16499cf889271fa4c029a6ea108d24c78bcc77f6729544
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/f4311e3ec7c47d3491f648d03dad91cf7a73b5d9/*"}}
1+{"documents":{"/Users/iko/Developer/skrepka/*":"https://raw.githubusercontent.com/ilyakooo0/skrepka/01f5f207fb604677f732e61a361f792f91a2b5c4/*"}}
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+57−57
@@ -506,10 +506,8 @@ countMessagesFor = \messages toKey t ->
506506validGossipEvents : [GossipEvent] -> Timestamp -> [ValidGossipEntry]
507507validGossipEvents = \events t -> do
508508 e <- events
509− where not (tooLong e.pubkey maxPubkeyLen)
510− where not (tooLong e.server maxServerNameLen)
511− where not (isBadServerName e.server)
512− where isValidHex e.pubkey
509+ where isValidHex e.pubkey && not (tooLong e.pubkey maxPubkeyLen)
510+ where not (tooLong e.server maxServerNameLen) && not (isBadServerName e.server)
513511 let server = normalizeServerName e.server
514512 where not (server == serverHostN)
515513 where e.timestamp <= t + clockSkewTolerance
@@ -590,12 +588,12 @@ sendBatchedGossip : [Text] -> [GossipEvent] -> IO {network} {}
590588sendBatchedGossip = \peerServers allEvents ->
591589 sendToEach peerServers (\server -> sendBatchedGossipToServer server allEvents)
592590
593−broadcastOfflineGossip = \t peerServers expiredLocal -> do
594− let expiredEntries = mergeByKey (\a b -> a.pubkey == b.pubkey) [] (do
595− p <- expiredLocal
596− let ttl = clamp offlineGossipMinTtl maxPresenceTtl ((p.expiresAt - p.timestamp) / 1000)
597− yield {pubkey: p.pubkey, ttl: ttl, expiresAt: p.expiresAt})
598− when (federationEnabled && count expiredEntries > 0 && count peerServers > 0) (do
591+broadcastOfflineGossip = \t peerServers expiredLocal ->
592+ when (federationEnabled && count peerServers > 0 && count expiredLocal > 0) (do
593+ let expiredEntries = mergeByKey (\a b -> a.pubkey == b.pubkey) [] (do
594+ p <- expiredLocal
595+ let ttl = clamp offlineGossipMinTtl maxPresenceTtl ((p.expiresAt - p.timestamp) / 1000)
596+ yield {pubkey: p.pubkey, ttl: ttl, expiresAt: p.expiresAt})
599597 let gossipEvents = do
600598 e <- expiredEntries
601599 yield {eventType: "offline", pubkey: e.pubkey, server: serverHostN, timestamp: e.expiresAt, ttl: e.ttl}
@@ -767,20 +765,18 @@ serve = \req -> case req of
767765 then collectPeerServers updatedPresence (\p -> p.server)
768766 else []
769767 yield {pr: pr, verify: VerifyOk {peerServers: peerServers}}
770− fork (do
771− case result.verify of
772− VerifyOk {peerServers} ->
773− when federationEnabled
774− (sendToEach 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− _ -> yield {}
780− let expiredLocal = case result.verify of
781− VerifyOk _ -> do p <- result.pr.expiredLocal; where not (p.pubkey == pubkey); yield p
782− _ -> result.pr.expiredLocal
783− broadcastOfflineGossip t result.pr.peerServers expiredLocal)
768+ fork (case result.verify of
769+ VerifyOk {peerServers} -> do
770+ when federationEnabled
771+ (sendToEach peerServers (\server -> do
772+ _ <- fetch (federationScheme ++ server) (RecvGossip {
773+ authorization: "Bearer " ++ federationSecret,
774+ events: [{eventType: "online", pubkey: pubkey, server: serverHostN, timestamp: t, ttl: onlineGossipTtl}], hopsRemaining: 2, fromServer: serverHostN})
775+ yield {}))
776+ broadcastOfflineGossip t result.pr.peerServers
777+ (do p <- result.pr.expiredLocal; where not (p.pubkey == pubkey); yield p)
778+ _ ->
779+ broadcastOfflineGossip t result.pr.peerServers result.pr.expiredLocal)
784780 yield (case result.verify of
785781 VerifyFailed _ -> respond {token: "", expiresAt: 0}
786782 VerifyOk _ -> respond {token: token, expiresAt: expiry})
@@ -800,13 +796,14 @@ serve = \req -> case req of
800796 SendMessage {to, encryptedBlob, timestamp, authorization, respond} -> do
801797 let to = normalizeHex to
802798 let encryptedBlob = normalizeHex encryptedBlob
799+ let token = extractToken authorization
803800 t <- now
804801 msgId <- genToken
805802 let clampedTs = clampTimestamp timestamp t
806803 result <- atomic do
807804 pr <- maybePrune t
808805 sessions <- *sessions
809− let auth = authenticateSession sessions (extractToken authorization) t
806+ let auth = authenticateSession sessions token t
810807 case auth of
811808 Unauthorized {} ->
812809 yield {pr: pr, send: SendUnauthorized {}}
@@ -855,12 +852,13 @@ serve = \req -> case req of
855852 SendDone {status} -> respond {status: status, messageId: msgId})
856853
857854 AckMessages {messageIds, authorization, respond} -> do
855+ let token = extractToken authorization
858856 t <- now
859857 let dedupedIds = dedupMessageIds messageIds
860858 result <- atomic do
861859 pr <- maybePrune t
862860 sessions <- *sessions
863− let auth = authenticateSession sessions (extractToken authorization) t
861+ let auth = authenticateSession sessions token t
864862 case auth of
865863 Unauthorized {} ->
866864 yield {pr: pr, ack: AckUnauthorized {}}
@@ -888,35 +886,36 @@ serve = \req -> case req of
888886 yield {pr: pr, ack: AckDone {pendingAcks: ackedFederated, evictedEntries: evictedEntries}}
889887 fork (do
890888 case result.ack of
891− AckDone {pendingAcks, evictedEntries} -> do
892− let originServers = collectPeerServers pendingAcks (\a -> a.originServer)
893− when (count originServers > 0 && federationEnabled)
894− (sendAcksAndClean pendingAcks originServers)
895− let evictedToRetry = do
896− a <- evictedEntries
897− where none pendingAcks (\a2 -> a2.messageId == a.messageId && a2.toKey == a.toKey)
898− yield a
899− when (count evictedToRetry > 0 && federationEnabled) (do
900− let evictedOriginServers = collectPeerServers evictedToRetry (\a -> a.originServer)
901− sendToEach evictedOriginServers (\server -> do
902− fetchResult <- fetch (federationScheme ++ server) (FederationAck {
903− authorization: "Bearer " ++ federationSecret,
904− messageIds: (do a <- evictedToRetry; where a.originServer == server; yield a.messageId),
905− originServer: server, fromServer: serverHostN})
906− case fetchResult of
907− Err _ -> do
908− dropped <- atomic do
909− ackedIds <- *ackedIds
910− let reinsert = do a <- evictedToRetry; where a.originServer == server; yield a
911− let combined = union ackedIds reinsert
912− let excess = count combined - maxAckedIds
913− let afterEviction = if excess <= 0 then combined
914− else evictOldestAckedIds combined excess
915− set *ackedIds = afterEviction
916− yield excess
917− when (dropped > 0)
918− (println ("ackMessages: ackedIds at capacity (" ++ show maxAckedIds ++ "), " ++ show dropped ++ " ack entries permanently lost for " ++ server))
919− _ -> yield {}))
889+ AckDone {pendingAcks, evictedEntries} ->
890+ when federationEnabled (do
891+ let originServers = collectPeerServers pendingAcks (\a -> a.originServer)
892+ when (count originServers > 0)
893+ (sendAcksAndClean pendingAcks originServers)
894+ let evictedToRetry = do
895+ a <- evictedEntries
896+ where none pendingAcks (\a2 -> a2.messageId == a.messageId && a2.toKey == a.toKey)
897+ yield a
898+ when (count evictedToRetry > 0) (do
899+ let evictedOriginServers = collectPeerServers evictedToRetry (\a -> a.originServer)
900+ sendToEach evictedOriginServers (\server -> do
901+ fetchResult <- fetch (federationScheme ++ server) (FederationAck {
902+ authorization: "Bearer " ++ federationSecret,
903+ messageIds: (do a <- evictedToRetry; where a.originServer == server; yield a.messageId),
904+ originServer: server, fromServer: serverHostN})
905+ case fetchResult of
906+ Err _ -> do
907+ dropped <- atomic do
908+ ackedIds <- *ackedIds
909+ let reinsert = do a <- evictedToRetry; where a.originServer == server; yield a
910+ let combined = union ackedIds reinsert
911+ let excess = count combined - maxAckedIds
912+ let afterEviction = if excess <= 0 then combined
913+ else evictOldestAckedIds combined excess
914+ set *ackedIds = afterEviction
915+ yield excess
916+ when (dropped > 0)
917+ (println ("ackMessages: ackedIds at capacity (" ++ show maxAckedIds ++ "), " ++ show dropped ++ " ack entries permanently lost for " ++ server))
918+ _ -> yield {})))
920919 _ -> yield {}
921920 broadcastOfflineGossip t result.pr.peerServers result.pr.expiredLocal)
922921 yield (case result.ack of
@@ -925,11 +924,12 @@ serve = \req -> case req of
925924
926925 LookupPresence {pubkey, authorization, respond} -> do
927926 let pubkey = normalizeHex pubkey
927+ let token = extractToken authorization
928928 t <- now
929929 result <- atomic do
930930 pr <- maybePrune t
931931 sessions <- *sessions
932− let auth = authenticateSession sessions (extractToken authorization) t
932+ let auth = authenticateSession sessions token t
933933 case auth of
934934 Unauthorized {} ->
935935 yield {pr: pr, lookup: LookupUnauthorized {}}
@@ -953,7 +953,7 @@ serve = \req -> case req of
953953 RecvGossip {events, hopsRemaining, fromServer, authorization, respond} -> do
954954 t <- now
955955 let authorized = verifyFederationAuth authorization
956− let cappedHops = if hopsRemaining > 5 then 5 else hopsRemaining
956+ let cappedHops = clamp 0 5 hopsRemaining
957957 let fromServer = normalizeServerName fromServer
958958 if not authorized || cappedHops < 1 || fromServer == serverHostN || count events > maxGossipEvents
959959 then do