on master help sign in
Simplify code and improve type safety: use wildcard patterns, factor broadcastOfflineGossip, bind token once, combine where clauses, type LookupResult.foundServer as OptionalServerName
01f5f20iko
client/App.fs+19−19
@@ -99,7 +99,9 @@ module App =
99 messages |> Map.tryFind pk |> Option.defaultValue []
99 messages |> Map.tryFind pk |> Option.defaultValue []
100
100
101 let private appendMessage (pk: string) (msg: ChatMessage) (messages: Map<string, ChatMessage list>) =
101 let private appendMessage (pk: string) (msg: ChatMessage) (messages: Map<string, ChatMessage list>) =
102− messages |> Map.add pk (messagesFor pk messages @ [ msg ])
102+ let existing = messagesFor pk messages
103+ if existing |> List.exists (fun m -> m.Id = msg.Id) then messages
104+ else Map.add pk (existing @ [ msg ]) messages
103
105
104 let private addContactIfNew (contact: Contact) (contacts: Contact list) =
106 let private addContactIfNew (contact: Contact) (contacts: Contact list) =
105 if contacts |> List.exists (fun c -> c.Pubkey = contact.Pubkey) then contacts
107 if contacts |> List.exists (fun c -> c.Pubkey = contact.Pubkey) then contacts
@@ -199,12 +201,9 @@ module App =
199 incoming
201 incoming
200 |> List.fold
202 |> List.fold
201 (fun m (fromPubkey, chatMsg) ->
203 (fun m (fromPubkey, chatMsg) ->
202− if messagesFor fromPubkey m.Messages |> List.exists (fun x -> x.Id = chatMsg.Id) then
203− m
204− else
205− { m with
204+ { m with
206− Contacts = m.Contacts |> addContactIfNew { Pubkey = fromPubkey; Nickname = truncKey fromPubkey }
205+ Contacts = m.Contacts |> addContactIfNew { Pubkey = fromPubkey; Nickname = truncKey fromPubkey }
207− Messages = m.Messages |> appendMessage fromPubkey chatMsg })
206+ Messages = m.Messages |> appendMessage fromPubkey chatMsg })
208 { model with PollStatus = status; PollCursor = newCursor }
207 { model with PollStatus = status; PollCursor = newCursor }
209
208
210 let cmds =
209 let cmds =
@@ -212,7 +211,7 @@ module App =
212 | Some session -> [ CmdPoll(session, model'.PollCursor) ]
211 | Some session -> [ CmdPoll(session, model'.PollCursor) ]
213 | None -> []
212 | None -> []
214
213
215− model', cmds @ (if not incoming.IsEmpty then saveCmd model' else [])
214+ model', cmds @ (if incoming.IsEmpty then [] else saveCmd model')
216
215
217 | SetNewPubkey pk ->
216 | SetNewPubkey pk ->
218 match model.Page with
217 match model.Page with
@@ -245,19 +244,19 @@ module App =
245 | DismissError -> { model with Error = None }, []
244 | DismissError -> { model with Error = None }, []
246
245
247 | StateLoaded(identity, dataOpt) ->
246 | StateLoaded(identity, dataOpt) ->
248− let model' = { model with Identity = Some identity; Conn = Connecting }
249−
250 let model' =
247 let model' =
251 match dataOpt with
248 match dataOpt with
252 | Some data ->
249 | Some data ->
253− { model' with
250+ { model with
251+ Identity = Some identity
252+ Conn = Connecting
254 Contacts = data.Contacts
253 Contacts = data.Contacts
255 Messages = data.Messages
254 Messages = data.Messages
256 ServerUrl = if data.ServerUrl <> "" then data.ServerUrl else model.ServerUrl
255 ServerUrl = if data.ServerUrl <> "" then data.ServerUrl else model.ServerUrl
257 PollCursor = data.PollCursor
256 PollCursor = data.PollCursor
258 Page = Conversations }
257 Page = Conversations }
259−
258+ | None ->
260− | None -> { model' with Page = Settings }
259+ { model with Identity = Some identity; Conn = Connecting; Page = Settings }
261
260
262 model', [ CmdConnect(model'.ServerUrl, identity) ]
261 model', [ CmdConnect(model'.ServerUrl, identity) ]
263
262
@@ -327,11 +326,12 @@ module App =
327 try
326 try
328 let! response = ApiClient.poll session.Url session.Token cursor
327 let! response = ApiClient.poll session.Url session.Token cursor
329 let messages, ackIds, errors =
328 let messages, ackIds, errors =
330− response.Events
329+ Array.foldBack
331− |> Array.fold (fun (msgs, acks, errs) evt ->
330+ (fun (evt: ApiClient.PollEvent) (msgs, acks, errs) ->
332− match decryptEvent session.Identity.PrivKey evt.Payload with
331+ match decryptEvent session.Identity.PrivKey evt.Payload with
333− | Ok msg -> (msg :: msgs, evt.Id :: acks, errs)
332+ | Ok msg -> (msg :: msgs, evt.Id :: acks, errs)
334− | Error err -> (msgs, acks, err :: errs))
333+ | Error err -> (msgs, acks, err :: errs))
334+ response.Events
335 ([], [], [])
335 ([], [], [])
336
336
337 if not ackIds.IsEmpty then
337 if not ackIds.IsEmpty then
@@ -343,7 +343,7 @@ module App =
343 $"[{now}] evts:{response.Events.Length} msgs:{messages.Length} errs:{errors.Length}"
343 $"[{now}] evts:{response.Events.Length} msgs:{messages.Length} errs:{errors.Length}"
344 + (match errors with err :: _ -> $" [{err}]" | [] -> "")
344 + (match errors with err :: _ -> $" [{err}]" | [] -> "")
345
345
346− return PollResult(List.rev messages, status, response.Cursor)
346+ return PollResult(messages, status, response.Cursor)
347 with
347 with
348 | :? TimeoutException ->
348 | :? TimeoutException ->
349 return PollResult([], "polling...", cursor)
349 return PollResult([], "polling...", cursor)
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+15−4
@@ -32,6 +32,10 @@
32 <data>
32 <data>
33 vxA0uDp+BuUEE5Xf1wcoVCW/EYs=
33 vxA0uDp+BuUEE5Xf1wcoVCW/EYs=
34 </data>
34 </data>
35+ <key>MauiInfo.plist</key>
36+ <data>
37+ iEtuUGCV6JM3gxS/pO33swhmpp8=
38+ </data>
35 <key>MauiSplash.storyboardc/01J-lp-oVM-view-Ze5-6b-2t3.nib</key>
39 <key>MauiSplash.storyboardc/01J-lp-oVM-view-Ze5-6b-2t3.nib</key>
36 <data>
40 <data>
37 Y78DTyz7+rkuDbhphGJe+YK2rLQ=
41 Y78DTyz7+rkuDbhphGJe+YK2rLQ=
@@ -162,11 +166,11 @@
162 </data>
166 </data>
163 <key>Skrepka.dll</key>
167 <key>Skrepka.dll</key>
164 <data>
168 <data>
165− fWgnzhQubQm/pXCMxdaeASe90IU=
169+ 1YPTBw0a3jfRaHp/FqqEqcamsFo=
166 </data>
170 </data>
167 <key>Skrepka.pdb</key>
171 <key>Skrepka.pdb</key>
168 <data>
172 <data>
169− EreX5kfzNb2S+VnlrBsyMswiGnM=
173+ 318b9V+eeWqqTJ1X1o5LHMWnywY=
170 </data>
174 </data>
171 <key>Sodium.Core.dll</key>
175 <key>Sodium.Core.dll</key>
172 <data>
176 <data>
@@ -1161,6 +1165,13 @@
1161 rjGsapNUkhe56LgUl9HugxZYrebfnh7iDyg48i3l4hg=
1165 rjGsapNUkhe56LgUl9HugxZYrebfnh7iDyg48i3l4hg=
1162 </data>
1166 </data>
1163 </dict>
1167 </dict>
1168+ <key>MauiInfo.plist</key>
1169+ <dict>
1170+ <key>hash2</key>
1171+ <data>
1172+ tx0KXqEC+DvcCGyogQTEQWHcCWJYYqH24NtPwhpQEjo=
1173+ </data>
1174+ </dict>
1164 <key>MauiSplash.storyboardc/01J-lp-oVM-view-Ze5-6b-2t3.nib</key>
1175 <key>MauiSplash.storyboardc/01J-lp-oVM-view-Ze5-6b-2t3.nib</key>
1165 <dict>
1176 <dict>
1166 <key>hash2</key>
1177 <key>hash2</key>
@@ -1382,14 +1393,14 @@
1382 <dict>
1393 <dict>
1383 <key>hash2</key>
1394 <key>hash2</key>
1384 <data>
1395 <data>
1385− kLMnGG+nlD35s66fyt9JcYg7ILRHQOazpZrn0F9ecAs=
1396+ w1+88siJc/avY1PQmPGLGB46y4akJb8sDoczgEVI4fM=
1386 </data>
1397 </data>
1387 </dict>
1398 </dict>
1388 <key>Skrepka.pdb</key>
1399 <key>Skrepka.pdb</key>
1389 <dict>
1400 <dict>
1390 <key>hash2</key>
1401 <key>hash2</key>
1391 <data>
1402 <data>
1392− SO6AxLeTTGVHAEWR0d25BmHOxy5yz4NIYe7juescHjM=
1403+ gaPNj44wAMOFLNjNCIJfaKHiU9IgJDuOQItWdMdbM2Y=
1393 </data>
1404 </data>
1394 </dict>
1405 </dict>
1395 <key>Sodium.Core.dll</key>
1406 <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+13−2
@@ -12,6 +12,10 @@
12 <data>
12 <data>
13 c3wF+z6XtMjJ+O5rJymqsoYbzDg=
13 c3wF+z6XtMjJ+O5rJymqsoYbzDg=
14 </data>
14 </data>
15+ <key>Resources/MauiInfo.plist</key>
16+ <data>
17+ iEtuUGCV6JM3gxS/pO33swhmpp8=
18+ </data>
15 <key>Resources/MonoTouchDebugConfiguration.txt</key>
19 <key>Resources/MonoTouchDebugConfiguration.txt</key>
16 <data>
20 <data>
17 oowkf1sL6T19nanXUObgmuayWqM=
21 oowkf1sL6T19nanXUObgmuayWqM=
@@ -247,14 +251,14 @@
247 <dict>
251 <dict>
248 <key>hash2</key>
252 <key>hash2</key>
249 <data>
253 <data>
250− Ll855YGoZZ4EHbGdnm7YJ2/UrFtklAGRlFW9NlLbdXI=
254+ ACzMr65iZCRxNkmPNzA6fyn/yv2+20NTfHo+2J3hOVo=
251 </data>
255 </data>
252 </dict>
256 </dict>
253 <key>MonoBundle/Skrepka.pdb</key>
257 <key>MonoBundle/Skrepka.pdb</key>
254 <dict>
258 <dict>
255 <key>hash2</key>
259 <key>hash2</key>
256 <data>
260 <data>
257− J4e+LdQyWbJGoueMWBhlFQg9tI6hYFwkIT6qA3pNgNU=
261+ 7pMDhAJJCLuEC9ASqtHFdvdW1xJFyw0foQ6ySH91mrU=
258 </data>
262 </data>
259 </dict>
263 </dict>
260 <key>MonoBundle/Sodium.Core.dll</key>
264 <key>MonoBundle/Sodium.Core.dll</key>
@@ -1790,6 +1794,13 @@
1790 HfYxSjf/+d0hBTO933j7O0S+icn+eoL8QQEDzydpDZM=
1794 HfYxSjf/+d0hBTO933j7O0S+icn+eoL8QQEDzydpDZM=
1791 </data>
1795 </data>
1792 </dict>
1796 </dict>
1797+ <key>Resources/MauiInfo.plist</key>
1798+ <dict>
1799+ <key>hash2</key>
1800+ <data>
1801+ tx0KXqEC+DvcCGyogQTEQWHcCWJYYqH24NtPwhpQEjo=
1802+ </data>
1803+ </dict>
1793 <key>Resources/MonoTouchDebugConfiguration.txt</key>
1804 <key>Resources/MonoTouchDebugConfiguration.txt</key>
1794 <dict>
1805 <dict>
1795 <key>hash2</key>
1806 <key>hash2</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
10[<assembly: System.Reflection.AssemblyCompanyAttribute("Skrepka")>]
10[<assembly: System.Reflection.AssemblyCompanyAttribute("Skrepka")>]
11[<assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")>]
11[<assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")>]
12[<assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")>]
12[<assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")>]
13−[<assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0+64e0fdcd67fd42117a3538c089bb99c276931127")>]
13+[<assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0+f4311e3ec7c47d3491f648d03dad91cf7a73b5d9")>]
14[<assembly: System.Reflection.AssemblyProductAttribute("Skrepka")>]
14[<assembly: System.Reflection.AssemblyProductAttribute("Skrepka")>]
15[<assembly: System.Reflection.AssemblyTitleAttribute("Skrepka")>]
15[<assembly: System.Reflection.AssemblyTitleAttribute("Skrepka")>]
16[<assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")>]
16[<assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")>]
client/obj/Debug/net10.0-ios/iossimulator-arm64/Skrepka.AssemblyInfoInputs.cache+1−1
@@ -1 +1 @@
1−e50f958afa3607e0c2176340e354fd0c5d565507a4ce35d8cd858d8feeb3f6a1
1+1e95aacb0126fb0fa0c97c9af194457ae6f1585fbb05c5244efbdd807cb04e1c
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
@@ -333,27 +333,4 @@
333/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/Skrepka.pdb
333/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/Skrepka.pdb
334/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/Skrepka.genruntimeconfig.cache
334/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/Skrepka.genruntimeconfig.cache
335/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/ref/Skrepka.dll
335/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
358/Users/iko/Developer/skrepka/client/bin/Debug/net10.0-ios/iossimulator-arm64/LiteDB.dll
336/Users/iko/Developer/skrepka/client/bin/Debug/net10.0-ios/iossimulator-arm64/LiteDB.dll
359−/Users/iko/Developer/skrepka/client/obj/Debug/net10.0-ios/iossimulator-arm64/resourcestamps/LiteDB.stamp
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/64e0fdcd67fd42117a3538c089bb99c276931127/*"}}
1+{"documents":{"/Users/iko/Developer/skrepka/*":"https://raw.githubusercontent.com/ilyakooo0/skrepka/f4311e3ec7c47d3491f648d03dad91cf7a73b5d9/*"}}
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
10[<assembly: System.Reflection.AssemblyCompanyAttribute("Skrepka")>]
10[<assembly: System.Reflection.AssemblyCompanyAttribute("Skrepka")>]
11[<assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")>]
11[<assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")>]
12[<assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")>]
12[<assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")>]
13−[<assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0+e692eb5d42ef866622de97d2bc08d09fdb62a11b")>]
13+[<assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0+f4311e3ec7c47d3491f648d03dad91cf7a73b5d9")>]
14[<assembly: System.Reflection.AssemblyProductAttribute("Skrepka")>]
14[<assembly: System.Reflection.AssemblyProductAttribute("Skrepka")>]
15[<assembly: System.Reflection.AssemblyTitleAttribute("Skrepka")>]
15[<assembly: System.Reflection.AssemblyTitleAttribute("Skrepka")>]
16[<assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")>]
16[<assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")>]
client/obj/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.AssemblyInfoInputs.cache+1−1
@@ -1 +1 @@
1−4dcdec3fa3f1ebbb1a84fb886e9dd06477af7c654b1d2094630d80c574da8beb
1+0e18d76dae776b5ef2d17435d35ec648e83dd873ca4b5da4371eead24ce9a533
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/e692eb5d42ef866622de97d2bc08d09fdb62a11b/*"}}
1+{"documents":{"/Users/iko/Developer/skrepka/*":"https://raw.githubusercontent.com/ilyakooo0/skrepka/f4311e3ec7c47d3491f648d03dad91cf7a73b5d9/*"}}
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+53−59
@@ -74,7 +74,7 @@ type PollResult = PollUnauthorized {} | PollAuthorized {events: [PollEvent], cur
74type VerifyResult = VerifyFailed {} | VerifyOk {peerServers: [Text]}
74type VerifyResult = VerifyFailed {} | VerifyOk {peerServers: [Text]}
75type SendResult = SendUnauthorized {} | SendRejected {} | SendDone {status: MessageStatus, remoteServers: [Text], fromKey: PubkeyHex}
75type SendResult = SendUnauthorized {} | SendRejected {} | SendDone {status: MessageStatus, remoteServers: [Text], fromKey: PubkeyHex}
76type AckResult = AckUnauthorized {} | AckDone {pendingAcks: [AckedEntry], evictedEntries: [AckedEntry]}
76type AckResult = AckUnauthorized {} | AckDone {pendingAcks: [AckedEntry], evictedEntries: [AckedEntry]}
77−type LookupResult = LookupUnauthorized {} | LookupDone {foundServer: Text}
77+type LookupResult = LookupUnauthorized {} | LookupDone {foundServer: OptionalServerName}
78
78
79
79
80-- ============================================
80-- ============================================
@@ -151,6 +151,10 @@ route Api = AuthApi | ClientApi | FederationApi | MaintenanceApi
151any = \xs pred -> fold (\acc x -> if acc then acc else pred x) (False {}) xs
151any = \xs pred -> fold (\acc x -> if acc then acc else pred x) (False {}) xs
152none = \xs pred -> not (any xs pred)
152none = \xs pred -> not (any xs pred)
153when = \cond action -> if cond then action else yield {}
153when = \cond action -> if cond then action else yield {}
154+whenOk = \result action -> case result of
155+ Ok _ -> action
156+ Err _ -> yield {}
157+clamp = \lo hi x -> if x < lo then lo else if x > hi then hi else x
154headOf = \items -> fold (\acc x -> case acc of
158headOf = \items -> fold (\acc x -> case acc of
155 Nothing {} -> Just {value: x}
159 Nothing {} -> Just {value: x}
156 _ -> acc) Nothing {} items
160 _ -> acc) Nothing {} items
@@ -204,11 +208,6 @@ isAllowedServerChar = \c ->
204 || c == "m" || c == "n" || c == "o" || c == "p" || c == "q" || c == "r"
208 || c == "m" || c == "n" || c == "o" || c == "p" || c == "q" || c == "r"
205 || c == "s" || c == "t" || c == "u" || c == "v" || c == "w" || c == "x"
209 || c == "s" || c == "t" || c == "u" || c == "v" || c == "w" || c == "x"
206 || c == "y" || c == "z"
210 || c == "y" || c == "z"
207− || c == "A" || c == "B" || c == "C" || c == "D" || c == "E" || c == "F"
208− || c == "G" || c == "H" || c == "I" || c == "J" || c == "K" || c == "L"
209− || c == "M" || c == "N" || c == "O" || c == "P" || c == "Q" || c == "R"
210− || c == "S" || c == "T" || c == "U" || c == "V" || c == "W" || c == "X"
211− || c == "Y" || c == "Z"
212
211
213containsBadUrlChar = \s ->
212containsBadUrlChar = \s ->
214 if s == "" then False {}
213 if s == "" then False {}
@@ -373,9 +372,9 @@ authenticateSession = \sessions token t ->
373 Just {value: s} -> Authorized {pubkey: s.pubkey}
372 Just {value: s} -> Authorized {pubkey: s.pubkey}
374
373
375extractToken = \authHeader ->
374extractToken = \authHeader ->
375+ let token = drop 7 authHeader in
376− if toLower (take 7 authHeader) == "bearer "
376+ if toLower (take 7 authHeader) == "bearer " && not (tooLong token maxTokenLen)
377− && not (tooLong (drop 7 authHeader) maxTokenLen)
377+ then token
378− then drop 7 authHeader
379 else ""
378 else ""
380
379
381verifySecret = \token secret ->
380verifySecret = \token secret ->
@@ -388,9 +387,9 @@ verifyMaintenanceAuth = \authorization ->
388 let token = extractToken authorization in
387 let token = extractToken authorization in
389 verifySecret token federationSecret || verifySecret token maintenanceSecret
388 verifySecret token federationSecret || verifySecret token maintenanceSecret
390
389
391−clampTtl = \ttl -> if ttl < 1 then 1 else if ttl > maxPresenceTtl then maxPresenceTtl else ttl
390+clampTtl = clamp 1 maxPresenceTtl
392
391
393−clampTimestamp = \timestamp t -> if timestamp > t then t else if timestamp < t - messageMaxAge then t - messageMaxAge else timestamp
392+clampTimestamp = \timestamp t -> clamp (t - messageMaxAge) t timestamp
394
393
395nextSeqTs = \t seqCounter -> if t > seqCounter then t else seqCounter + 1
394nextSeqTs = \t seqCounter -> if t > seqCounter then t else seqCounter + 1
396
395
@@ -500,8 +499,7 @@ removeOffline = \presenceSet offlineOnlyKeys trustedOffline t ->
500countMessagesFor = \messages toKey t ->
499countMessagesFor = \messages toKey t ->
501 count (do
500 count (do
502 m <- messages
501 m <- messages
503− where m.toKey == toKey
504− where m.receivedAt >= t - messageMaxAge
502+ where m.toKey == toKey && m.receivedAt >= t - messageMaxAge
505 yield m)
503 yield m)
506
504
507
505
@@ -595,7 +593,8 @@ sendBatchedGossip = \peerServers allEvents ->
595broadcastOfflineGossip = \t peerServers expiredLocal -> do
593broadcastOfflineGossip = \t peerServers expiredLocal -> do
596 let expiredEntries = mergeByKey (\a b -> a.pubkey == b.pubkey) [] (do
594 let expiredEntries = mergeByKey (\a b -> a.pubkey == b.pubkey) [] (do
597 p <- expiredLocal
595 p <- expiredLocal
598− yield {pubkey: p.pubkey, ttl: (let rawTtl = (p.expiresAt - p.timestamp) / 1000 in if rawTtl < offlineGossipMinTtl then offlineGossipMinTtl else if rawTtl > maxPresenceTtl then maxPresenceTtl else rawTtl), expiresAt: p.expiresAt})
596+ let ttl = clamp offlineGossipMinTtl maxPresenceTtl ((p.expiresAt - p.timestamp) / 1000)
597+ yield {pubkey: p.pubkey, ttl: ttl, expiresAt: p.expiresAt})
599 when (federationEnabled && count expiredEntries > 0 && count peerServers > 0) (do
598 when (federationEnabled && count expiredEntries > 0 && count peerServers > 0) (do
600 let gossipEvents = do
599 let gossipEvents = do
601 e <- expiredEntries
600 e <- expiredEntries
@@ -605,6 +604,10 @@ broadcastOfflineGossip = \t peerServers expiredLocal -> do
605forkBroadcastOffline = \t result ->
604forkBroadcastOffline = \t result ->
606 fork (broadcastOfflineGossip t result.peerServers result.expiredLocal)
605 fork (broadcastOfflineGossip t result.peerServers result.expiredLocal)
607
606
607+maybePruneAndBroadcast = \t -> do
608+ pruneResult <- atomic do maybePrune t
609+ forkBroadcastOffline t pruneResult
610+
608removeForward = \mid srv -> atomic do
611removeForward = \mid srv -> atomic do
609 forwards <- *forwards
612 forwards <- *forwards
610 set *forwards = do
613 set *forwards = do
@@ -624,9 +627,7 @@ retryForwardsForServer = \entries remaining ->
624 toKey: f.toKey, fromKey: f.fromKey, encryptedBlob: f.encryptedBlob,
627 toKey: f.toKey, fromKey: f.fromKey, encryptedBlob: f.encryptedBlob,
625 timestamp: f.timestamp, originServer: serverHostN, messageId: f.messageId,
628 timestamp: f.timestamp, originServer: serverHostN, messageId: f.messageId,
626 authorization: "Bearer " ++ federationSecret})
629 authorization: "Bearer " ++ federationSecret})
627− case result of
628− Ok _ -> removeForward f.messageId f.toServer
630+ whenOk result (removeForward f.messageId f.toServer)
629− Err _ -> yield {}
630 retryForwardsForServer rest (remaining - 1)
631 retryForwardsForServer rest (remaining - 1)
631
632
632retryPendingForwards : [ForwardEntry] -> IO {network} {}
633retryPendingForwards : [ForwardEntry] -> IO {network} {}
@@ -643,15 +644,12 @@ sendBatchedAcksToServer = \serverAcks server ->
643 authorization: "Bearer " ++ federationSecret,
644 authorization: "Bearer " ++ federationSecret,
644 messageIds: (do a <- batch; yield a.messageId),
645 messageIds: (do a <- batch; yield a.messageId),
645 originServer: server, fromServer: serverHostN})
646 originServer: server, fromServer: serverHostN})
646− case result of
647− Ok _ ->
648− atomic do
647+ whenOk result (atomic do
649− ackedIds <- *ackedIds
648+ ackedIds <- *ackedIds
650− set *ackedIds = do
649+ set *ackedIds = do
651− a <- ackedIds
650+ a <- ackedIds
652− where not (a.originServer == server && any batch (\sa -> sa.messageId == a.messageId && sa.toKey == a.toKey))
651+ where not (a.originServer == server && any batch (\sa -> sa.messageId == a.messageId && sa.toKey == a.toKey))
653− yield a
652+ yield a))
654− Err _ -> yield {})
655
653
656sendAcksAndClean : [{messageId: MessageIdHex, toKey: PubkeyHex, originServer: ServerName, | a}] -> [ServerName] -> IO {network} {}
654sendAcksAndClean : [{messageId: MessageIdHex, toKey: PubkeyHex, originServer: ServerName, | a}] -> [ServerName] -> IO {network} {}
657sendAcksAndClean = \pendingAcks servers ->
655sendAcksAndClean = \pendingAcks servers ->
@@ -685,12 +683,10 @@ pollOnce = \token cursor -> do
685 seqCounter <- *seqCounter
683 seqCounter <- *seqCounter
686 messages <- *messages
684 messages <- *messages
687 let cursorCeil = if seqCounter > t then seqCounter else t
685 let cursorCeil = if seqCounter > t then seqCounter else t
688− let clampedCursor = if cursor < 0 then 0 else if cursor > cursorCeil then cursorCeil else cursor
686+ let clampedCursor = clamp 0 cursorCeil cursor
689 let matchingMsgs = do
687 let matchingMsgs = do
690 m <- messages
688 m <- messages
691− where m.toKey == pubkey
692− where m.receivedAt >= t - messageMaxAge
689+ where m.toKey == pubkey && m.receivedAt >= t - messageMaxAge && m.receivedAt > clampedCursor
693− where m.receivedAt > clampedCursor
694 yield m
690 yield m
695 let limitedMsgs = boundedSmallest matchingMsgs maxPollEvents (\m -> m.receivedAt)
691 let limitedMsgs = boundedSmallest matchingMsgs maxPollEvents (\m -> m.receivedAt)
696 let evts = do
692 let evts = do
@@ -771,31 +767,32 @@ serve = \req -> case req of
771 then collectPeerServers updatedPresence (\p -> p.server)
767 then collectPeerServers updatedPresence (\p -> p.server)
772 else []
768 else []
773 yield {pr: pr, verify: VerifyOk {peerServers: peerServers}}
769 yield {pr: pr, verify: VerifyOk {peerServers: peerServers}}
770+ fork (do
774− fork (case result.verify of
771+ case result.verify of
775− VerifyOk {peerServers} -> do
772+ VerifyOk {peerServers} ->
776− when federationEnabled
773+ when federationEnabled
777− (sendToEach peerServers (\server -> do
774+ (sendToEach peerServers (\server -> do
778− _ <- fetch (federationScheme ++ server) (RecvGossip {
775+ _ <- fetch (federationScheme ++ server) (RecvGossip {
779− authorization: "Bearer " ++ federationSecret,
776+ authorization: "Bearer " ++ federationSecret,
780− events: [{eventType: "online", pubkey: pubkey, server: serverHostN, timestamp: t, ttl: onlineGossipTtl}], hopsRemaining: 2, fromServer: serverHostN})
777+ events: [{eventType: "online", pubkey: pubkey, server: serverHostN, timestamp: t, ttl: onlineGossipTtl}], hopsRemaining: 2, fromServer: serverHostN})
781− yield {}))
778+ yield {}))
779+ _ -> yield {}
780+ let expiredLocal = case result.verify of
782− broadcastOfflineGossip t result.pr.peerServers (do p <- result.pr.expiredLocal; where not (p.pubkey == pubkey); yield p)
781+ VerifyOk _ -> do p <- result.pr.expiredLocal; where not (p.pubkey == pubkey); yield p
783− VerifyFailed {} ->
782+ _ -> result.pr.expiredLocal
784− broadcastOfflineGossip t result.pr.peerServers result.pr.expiredLocal)
783+ broadcastOfflineGossip t result.pr.peerServers expiredLocal)
785 yield (case result.verify of
784 yield (case result.verify of
786 VerifyFailed _ -> respond {token: "", expiresAt: 0}
785 VerifyFailed _ -> respond {token: "", expiresAt: 0}
787 VerifyOk _ -> respond {token: token, expiresAt: expiry})
786 VerifyOk _ -> respond {token: token, expiresAt: expiry})
788
787
789 Poll {cursor, timeout, authorization, respond} -> do
788 Poll {cursor, timeout, authorization, respond} -> do
790 t <- now
789 t <- now
791− let clampedTimeout = if timeout < 0 then 0 else if timeout > 60000 then 60000 else timeout
790+ let clampedTimeout = clamp 0 60000 timeout
792 let deadline = t + clampedTimeout
791 let deadline = t + clampedTimeout
793 let token = extractToken authorization
792 let token = extractToken authorization
794 result <- pollLoop token cursor deadline
793 result <- pollLoop token cursor deadline
795 tAfterPoll <- now
794 tAfterPoll <- now
796− pruneResult <- atomic do
795+ maybePruneAndBroadcast tAfterPoll
797− maybePrune tAfterPoll
798− forkBroadcastOffline tAfterPoll pruneResult
799 yield (case result of
796 yield (case result of
800 PollUnauthorized {} -> respond {events: [], cursor: -1, authorized: False {}}
797 PollUnauthorized {} -> respond {events: [], cursor: -1, authorized: False {}}
801 PollAuthorized {events, cursor: c} -> respond {events: events, cursor: c, authorized: True {}})
798 PollAuthorized {events, cursor: c} -> respond {events: events, cursor: c, authorized: True {}})
@@ -825,7 +822,7 @@ serve = \req -> case req of
825 set *messages = union messages
822 set *messages = union messages
826 [{messageId: msgId, toKey: to, fromKey: fromKey,
823 [{messageId: msgId, toKey: to, fromKey: fromKey,
827 encryptedBlob: encryptedBlob, timestamp: clampedTs, originServer: "", receivedAt: seqTs}]
824 encryptedBlob: encryptedBlob, timestamp: clampedTs, originServer: "", receivedAt: seqTs}]
828− let toPresence = do p <- presence; where p.pubkey == to; where p.expiresAt > t; yield p
825+ let toPresence = do p <- presence; where p.pubkey == to && p.expiresAt > t; yield p
829 let localOnline = any toPresence (\p -> p.server == serverHostN)
826 let localOnline = any toPresence (\p -> p.server == serverHostN)
830 let remoteServers = do p <- toPresence; where not (p.server == serverHostN); yield p.server
827 let remoteServers = do p <- toPresence; where not (p.server == serverHostN); yield p.server
831 forwards <- *forwards
828 forwards <- *forwards
@@ -847,13 +844,10 @@ serve = \req -> case req of
847 toKey: to, fromKey: fromKey, encryptedBlob: encryptedBlob,
844 toKey: to, fromKey: fromKey, encryptedBlob: encryptedBlob,
848 timestamp: clampedTs, originServer: serverHostN, messageId: msgId,
845 timestamp: clampedTs, originServer: serverHostN, messageId: msgId,
849 authorization: "Bearer " ++ federationSecret})
846 authorization: "Bearer " ++ federationSecret})
850− case fwdResult of
851− Ok _ -> removeForward msgId server
847+ whenOk fwdResult (removeForward msgId server))
852− Err _ -> yield {})
853 when (status == "queued")
848 when (status == "queued")
854 (println ("sendMessage: forwards at capacity (" ++ show maxForwardsEntries ++ "), skipping forward tracking")))
849 (println ("sendMessage: forwards at capacity (" ++ show maxForwardsEntries ++ "), skipping forward tracking")))
855− SendUnauthorized {} -> yield {}
856− SendRejected {} -> yield {}
850+ _ -> yield {}
857 broadcastOfflineGossip t result.pr.peerServers result.pr.expiredLocal)
851 broadcastOfflineGossip t result.pr.peerServers result.pr.expiredLocal)
858 yield (case result.send of
852 yield (case result.send of
859 SendUnauthorized _ -> respond {status: "unauthorized", messageId: ""}
853 SendUnauthorized _ -> respond {status: "unauthorized", messageId: ""}
@@ -923,7 +917,7 @@ serve = \req -> case req of
923 when (dropped > 0)
917 when (dropped > 0)
924 (println ("ackMessages: ackedIds at capacity (" ++ show maxAckedIds ++ "), " ++ show dropped ++ " ack entries permanently lost for " ++ server))
918 (println ("ackMessages: ackedIds at capacity (" ++ show maxAckedIds ++ "), " ++ show dropped ++ " ack entries permanently lost for " ++ server))
925 _ -> yield {}))
919 _ -> yield {}))
926− AckUnauthorized {} -> yield {}
920+ _ -> yield {}
927 broadcastOfflineGossip t result.pr.peerServers result.pr.expiredLocal)
921 broadcastOfflineGossip t result.pr.peerServers result.pr.expiredLocal)
928 yield (case result.ack of
922 yield (case result.ack of
929 AckUnauthorized _ -> respond {ok: False {}}
923 AckUnauthorized _ -> respond {ok: False {}}
@@ -943,12 +937,13 @@ serve = \req -> case req of
943 presence <- *presence
937 presence <- *presence
944 let valid = do
938 let valid = do
945 p <- presence
939 p <- presence
946− where p.pubkey == pubkey
940+ where p.pubkey == pubkey && p.expiresAt > t
947− where p.expiresAt > t
948 yield p
941 yield p
949− let foundServer = if any valid (\p -> p.server == serverHostN)
942+ let foundServer = (fold (\acc p ->
950− then serverHostN
943+ if acc.server == serverHostN then acc
944+ else if p.server == serverHostN then {server: serverHostN, best: p.expiresAt}
951− else (fold (\acc p -> if p.expiresAt > acc.best then {server: p.server, best: p.expiresAt} else acc) {server: "", best: 0} valid).server
945+ else if p.expiresAt > acc.best then {server: p.server, best: p.expiresAt}
946+ else acc) {server: "", best: 0} valid).server
952 yield {pr: pr, lookup: LookupDone {foundServer: foundServer}}
947 yield {pr: pr, lookup: LookupDone {foundServer: foundServer}}
953 forkBroadcastOffline t result.pr
948 forkBroadcastOffline t result.pr
954 yield (case result.lookup of
949 yield (case result.lookup of
@@ -962,8 +957,7 @@ serve = \req -> case req of
962 let fromServer = normalizeServerName fromServer
957 let fromServer = normalizeServerName fromServer
963 if not authorized || cappedHops < 1 || fromServer == serverHostN || count events > maxGossipEvents
958 if not authorized || cappedHops < 1 || fromServer == serverHostN || count events > maxGossipEvents
964 then do
959 then do
965− pruneResult <- atomic do maybePrune t
960+ maybePruneAndBroadcast t
966− forkBroadcastOffline t pruneResult
967 yield (respond {ok: False {}})
961 yield (respond {ok: False {}})
968 else do
962 else do
969 let allValidEntries = validGossipEvents events t
963 let allValidEntries = validGossipEvents events t