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
5c34fabiko
client/ApiClient.fs+23−1
@@ -15,8 +15,10 @@ module ApiClient =
15 [<CLIMutable>]
15 [<CLIMutable>]
16 type PollEvent = { Id: string; EventType: string; Payload: EventPayload }
16 type PollEvent = { Id: string; EventType: string; Payload: EventPayload }
17
17
18+ type MessageStatus = Delivered | Federated | Queued | Rejected | Unauthorized
19+
18 [<CLIMutable>]
20 [<CLIMutable>]
19− type SendResult = { Status: string; MessageId: string }
21+ type SendResult = { Status: MessageStatus; MessageId: string }
20
22
21 [<CLIMutable>]
23 [<CLIMutable>]
22 type PollResponse = { Cursor: int64; Events: PollEvent array }
24 type PollResponse = { Cursor: int64; Events: PollEvent array }
@@ -45,9 +47,29 @@ module ApiClient =
45 | _ -> reader.GetInt64()
47 | _ -> reader.GetInt64()
46 override _.Write(writer, value, _) = writer.WriteNumberValue(value)
48 override _.Write(writer, value, _) = writer.WriteNumberValue(value)
47
49
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+
48 let private jsonOpts =
69 let private jsonOpts =
49 let opts = JsonSerializerOptions(PropertyNameCaseInsensitive = true)
70 let opts = JsonSerializerOptions(PropertyNameCaseInsensitive = true)
50 opts.Converters.Add(FlexibleInt64Converter())
71 opts.Converters.Add(FlexibleInt64Converter())
72+ opts.Converters.Add(MessageStatusConverter())
51 opts
73 opts
52
74
53 let private client =
75 let private client =
client/App.fs+52−41
@@ -32,18 +32,21 @@ module App =
32
32
33 type Session = { Url: string; Token: string; Identity: Identity }
33 type Session = { Url: string; Token: string; Identity: Identity }
34
34
35− type ConnState =
35+ type ConnStatus =
36 | Offline
36 | Offline
37 | Connecting
37 | Connecting
38− | Online of Session
38+ | Online of token: string
39+
40+ type AuthState =
41+ | NoIdentity
42+ | Identified of identity: Identity * conn: ConnStatus
39
43
40 // ── Model ──
44 // ── Model ──
41
45
42 type Model =
46 type Model =
43 { Page: Page
47 { Page: Page
44− Identity: Identity option
48+ Auth: AuthState
45 ServerUrl: string
49 ServerUrl: string
46− Conn: ConnState
47 Contacts: Contact list
50 Contacts: Contact list
48 PollStatus: string
51 PollStatus: string
49 Messages: Map<string, ChatMessage list>
52 Messages: Map<string, ChatMessage list>
@@ -114,13 +117,20 @@ module App =
114 contacts @ [ contact ]
117 contacts @ [ contact ]
115
118
116 let private pubKeyHex model =
119 let private pubKeyHex model =
117− model.Identity |> Option.map _.PubKeyHex |> Option.defaultValue ""
120+ match model.Auth with
121+ | Identified(id, _) -> id.PubKeyHex
122+ | NoIdentity -> ""
118
123
119 let private trySession model =
124 let private trySession model =
120− match model.Conn with
125+ match model.Auth with
121− | Online session -> Some session
126+ | Identified(id, Online token) -> Some { Url = model.ServerUrl; Token = token; Identity = id }
122 | _ -> None
127 | _ -> None
123
128
129+ let private setConn status model =
130+ match model.Auth with
131+ | Identified(id, _) -> { model with Auth = Identified(id, status) }
132+ | NoIdentity -> model
133+
124 let private saveCmd model =
134 let private saveCmd model =
125 [ CmdSaveData { Contacts = model.Contacts; Messages = model.Messages; ServerUrl = model.ServerUrl; PollCursor = model.PollCursor } ]
135 [ CmdSaveData { Contacts = model.Contacts; Messages = model.Messages; ServerUrl = model.ServerUrl; PollCursor = model.PollCursor } ]
126
136
@@ -128,9 +138,8 @@ module App =
128
138
129 let init () =
139 let init () =
130 { Page = Setup
140 { Page = Setup
131− Identity = None
141+ Auth = NoIdentity
132 ServerUrl = "http://localhost:8080"
142 ServerUrl = "http://localhost:8080"
133− Conn = Offline
134 PollStatus = ""
143 PollStatus = ""
135 Contacts = []
144 Contacts = []
136 Messages = Map.empty
145 Messages = Map.empty
@@ -147,28 +156,28 @@ module App =
147 | GenIdentity -> model, [ CmdGenIdentity ]
156 | GenIdentity -> model, [ CmdGenIdentity ]
148
157
149 | IdentityReady identity ->
158 | IdentityReady identity ->
150− { model with Identity = Some identity; Page = Settings },
159+ { model with Auth = Identified(identity, Offline); Page = Settings },
151 [ CmdSaveIdentity identity ]
160 [ CmdSaveIdentity identity ]
152
161
153 | SetServerUrl url -> { model with ServerUrl = url }, []
162 | SetServerUrl url -> { model with ServerUrl = url }, []
154
163
155 | DoConnect ->
164 | DoConnect ->
156− match model.Identity with
165+ match model.Auth with
157− | Some id ->
166+ | Identified(id, _) ->
158− { model with Conn = Connecting }, [ CmdConnect(model.ServerUrl, id) ]
167+ model |> setConn Connecting, [ CmdConnect(model.ServerUrl, id) ]
159− | None -> { model with Error = Some "No identity generated" }, []
168+ | NoIdentity -> { model with Error = Some "No identity generated" }, []
160
169
161 | AuthOk token ->
170 | AuthOk token ->
162− match model.Identity, model.Conn with
171+ match model.Auth with
163− | Some id, Connecting ->
172+ | Identified(id, Connecting) ->
164 let session = { Url = model.ServerUrl; Token = token; Identity = id }
173 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 },
166 [ CmdPoll(session, model.PollCursor) ]
175 [ CmdPoll(session, model.PollCursor) ]
167 | _ -> model, []
176 | _ -> model, []
168
177
169− | AuthErr err -> { model with Conn = Offline; Error = Some err }, []
178+ | AuthErr err -> { setConn Offline model with Error = Some err }, []
170
179
171− | DoDisconnect -> { model with Conn = Offline }, []
180+ | DoDisconnect -> model |> setConn Offline, []
172
181
173 | SetCompose text ->
182 | SetCompose text ->
174 match model.Page with
183 match model.Page with
@@ -193,14 +202,18 @@ module App =
193 model', CmdSend(session, pk, compose, id) :: saveCmd model'
202 model', CmdSend(session, pk, compose, id) :: saveCmd model'
194 | _ -> model, []
203 | _ -> model, []
195
204
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" }, []
196− | Sent(Ok _) -> model, []
209+ | _ -> model, []
197 | Sent(Error err) -> { model with Error = Some $"Send failed: {err}" }, []
210 | Sent(Error err) -> { model with Error = Some $"Send failed: {err}" }, []
198
211
199 | PollResult(incoming, status, newCursor) ->
212 | PollResult(incoming, status, newCursor) ->
200 let model' =
213 let model' =
201 incoming
214 incoming
202 |> List.fold
215 |> List.fold
203− (fun m (fromPubkey, chatMsg) ->
216+ (fun (m: Model) (fromPubkey, chatMsg) ->
204 { m with
217 { m with
205 Contacts = m.Contacts |> addContactIfNew { Pubkey = fromPubkey; Nickname = truncKey fromPubkey }
218 Contacts = m.Contacts |> addContactIfNew { Pubkey = fromPubkey; Nickname = truncKey fromPubkey }
206 Messages = m.Messages |> appendMessage fromPubkey chatMsg })
219 Messages = m.Messages |> appendMessage fromPubkey chatMsg })
@@ -237,28 +250,26 @@ module App =
237 | _ -> model, []
250 | _ -> model, []
238
251
239 | CopyPubKey ->
252 | CopyPubKey ->
240− match model.Identity with
253+ match model.Auth with
241− | Some id -> model, [ CmdCopyToClipboard id.PubKeyHex ]
254+ | Identified(id, _) -> model, [ CmdCopyToClipboard id.PubKeyHex ]
242− | None -> model, []
255+ | NoIdentity -> model, []
243
256
244 | DismissError -> { model with Error = None }, []
257 | DismissError -> { model with Error = None }, []
245
258
246 | StateLoaded(identity, dataOpt) ->
259 | StateLoaded(identity, dataOpt) ->
247− let model' =
248− match dataOpt with
260+ match dataOpt with
249− | Some data ->
261+ | Some data ->
262+ let model' =
250 { model with
263 { model with
251− Identity = Some identity
252− Conn = Connecting
264+ Auth = Identified(identity, Connecting)
253 Contacts = data.Contacts
265 Contacts = data.Contacts
254 Messages = data.Messages
266 Messages = data.Messages
255 ServerUrl = if data.ServerUrl <> "" then data.ServerUrl else model.ServerUrl
267 ServerUrl = if data.ServerUrl <> "" then data.ServerUrl else model.ServerUrl
256 PollCursor = data.PollCursor
268 PollCursor = data.PollCursor
257 Page = Conversations }
269 Page = Conversations }
270+ model', [ CmdConnect(model'.ServerUrl, identity) ]
258− | None ->
271+ | None ->
259− { model with Identity = Some identity; Conn = Connecting; Page = Settings }
272+ { model with Auth = Identified(identity, Offline); Page = Settings }, []
260−
261− model', [ CmdConnect(model'.ServerUrl, identity) ]
262
273
263 // ── mapCmd ──
274 // ── mapCmd ──
264
275
@@ -424,10 +435,10 @@ module App =
424 Button("Dismiss", DismissError)
435 Button("Dismiss", DismissError)
425 | None -> ()
436 | None -> ()
426
437
427− match model.Conn with
438+ match model.Auth with
428− | Offline -> Button("Connect", DoConnect)
439+ | Identified(_, Offline) | NoIdentity -> Button("Connect", DoConnect)
429− | Connecting -> Label("Connecting...").centerTextHorizontal()
440+ | Identified(_, Connecting) -> Label("Connecting...").centerTextHorizontal()
430− | Online _ ->
441+ | Identified(_, Online _) ->
431 Button("Disconnect", DoDisconnect)
442 Button("Disconnect", DoDisconnect)
432 Button("Go to Conversations", Nav Conversations)
443 Button("Go to Conversations", Nav Conversations)
433 })
444 })
@@ -437,10 +448,10 @@ module App =
437
448
438 let private viewConversations model =
449 let private viewConversations model =
439 let connStr =
450 let connStr =
440− match model.Conn with
451+ match model.Auth with
441− | Offline -> "OFFLINE"
452+ | NoIdentity | Identified(_, Offline) -> "OFFLINE"
442− | Connecting -> "CONNECTING"
453+ | Identified(_, Connecting) -> "CONNECTING"
443− | Online _ -> "ONLINE"
454+ | Identified(_, Online _) -> "ONLINE"
444
455
445 let contactNames =
456 let contactNames =
446 model.Contacts
457 model.Contacts
client/Store.fs+10−10
@@ -51,13 +51,13 @@ module Store =
51 SecureStorage.Default.GetAsync("identity_pubkey")
51 SecureStorage.Default.GetAsync("identity_pubkey")
52 |> Async.AwaitTask
52 |> Async.AwaitTask
53
53
54− if isNull privKeyB64 || isNull pubKeyHex then
55− return None
54+ return
56− else
55+ Option.map2
57− return
56+ (fun pk pub ->
58− Some
59− { Crypto.Identity.PrivKey = Convert.FromBase64String privKeyB64
57+ { Crypto.Identity.PrivKey = Convert.FromBase64String pk
60− Crypto.Identity.PubKeyHex = pubKeyHex }
58+ Crypto.Identity.PubKeyHex = pub })
59+ (Option.ofObj privKeyB64)
60+ (Option.ofObj pubKeyHex)
61 }
61 }
62
62
63 let saveIdentity (identity: Crypto.Identity) =
63 let saveIdentity (identity: Crypto.Identity) =
@@ -71,9 +71,9 @@ module Store =
71 use db = openDb ()
71 use db = openDb ()
72 let settings = db.GetCollection<SettingsDoc>("settings").FindById(BsonValue "settings")
72 let settings = db.GetCollection<SettingsDoc>("settings").FindById(BsonValue "settings")
73
73
74− if box settings |> isNull then
74+ match box settings with
75− None
75+ | null -> None
76− else
76+ | _ ->
77 let contacts = db.GetCollection<Contact>("contacts").FindAll() |> Seq.toList
77 let contacts = db.GetCollection<Contact>("contacts").FindAll() |> Seq.toList
78
78
79 let messages =
79 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 @@
166 </data>
166 </data>
167 <key>Skrepka.dll</key>
167 <key>Skrepka.dll</key>
168 <data>
168 <data>
169− 1YPTBw0a3jfRaHp/FqqEqcamsFo=
169+ 5V7ZArTTdWWdtF2EQw4DfWAWqsI=
170 </data>
170 </data>
171 <key>Skrepka.pdb</key>
171 <key>Skrepka.pdb</key>
172 <data>
172 <data>
173− 318b9V+eeWqqTJ1X1o5LHMWnywY=
173+ SsPgpZXl35VDuHCOYCegZuLqIVg=
174 </data>
174 </data>
175 <key>Sodium.Core.dll</key>
175 <key>Sodium.Core.dll</key>
176 <data>
176 <data>
@@ -1393,14 +1393,14 @@
1393 <dict>
1393 <dict>
1394 <key>hash2</key>
1394 <key>hash2</key>
1395 <data>
1395 <data>
1396− w1+88siJc/avY1PQmPGLGB46y4akJb8sDoczgEVI4fM=
1396+ EjrAHE9wqEvTYF+Siheu21r0UjeHDIZTT38w82FXRYU=
1397 </data>
1397 </data>
1398 </dict>
1398 </dict>
1399 <key>Skrepka.pdb</key>
1399 <key>Skrepka.pdb</key>
1400 <dict>
1400 <dict>
1401 <key>hash2</key>
1401 <key>hash2</key>
1402 <data>
1402 <data>
1403− gaPNj44wAMOFLNjNCIJfaKHiU9IgJDuOQItWdMdbM2Y=
1403+ cqhfrvM9zLFFTemz+KoZd7JckLQGVacy+ANck7D2x18=
1404 </data>
1404 </data>
1405 </dict>
1405 </dict>
1406 <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+2−2
@@ -251,14 +251,14 @@
251 <dict>
251 <dict>
252 <key>hash2</key>
252 <key>hash2</key>
253 <data>
253 <data>
254− ACzMr65iZCRxNkmPNzA6fyn/yv2+20NTfHo+2J3hOVo=
254+ fVOogHexeXD8oGEEo57l14SmUBycR/4qC7AH6BhBs7k=
255 </data>
255 </data>
256 </dict>
256 </dict>
257 <key>MonoBundle/Skrepka.pdb</key>
257 <key>MonoBundle/Skrepka.pdb</key>
258 <dict>
258 <dict>
259 <key>hash2</key>
259 <key>hash2</key>
260 <data>
260 <data>
261− 7pMDhAJJCLuEC9ASqtHFdvdW1xJFyw0foQ6ySH91mrU=
261+ ZOxyEQ1fCheg8RSrL3C6orb8q4+ouFNQ6lWNgWyrhPo=
262 </data>
262 </data>
263 </dict>
263 </dict>
264 <key>MonoBundle/Sodium.Core.dll</key>
264 <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
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+f4311e3ec7c47d3491f648d03dad91cf7a73b5d9")>]
13+[<assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0+01f5f207fb604677f732e61a361f792f91a2b5c4")>]
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−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 @@
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/bin/Debug/net10.0-ios/iossimulator-arm64/LiteDB.dll
336/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
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+f4311e3ec7c47d3491f648d03dad91cf7a73b5d9")>]
13+[<assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0+01f5f207fb604677f732e61a361f792f91a2b5c4")>]
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−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 ->
506validGossipEvents : [GossipEvent] -> Timestamp -> [ValidGossipEntry]
506validGossipEvents : [GossipEvent] -> Timestamp -> [ValidGossipEntry]
507validGossipEvents = \events t -> do
507validGossipEvents = \events t -> do
508 e <- events
508 e <- events
509− where not (tooLong e.pubkey maxPubkeyLen)
509+ where isValidHex e.pubkey && not (tooLong e.pubkey maxPubkeyLen)
510− where not (tooLong e.server maxServerNameLen)
510+ where not (tooLong e.server maxServerNameLen) && not (isBadServerName e.server)
511− where not (isBadServerName e.server)
512− where isValidHex e.pubkey
513 let server = normalizeServerName e.server
511 let server = normalizeServerName e.server
514 where not (server == serverHostN)
512 where not (server == serverHostN)
515 where e.timestamp <= t + clockSkewTolerance
513 where e.timestamp <= t + clockSkewTolerance
@@ -590,12 +588,12 @@ sendBatchedGossip : [Text] -> [GossipEvent] -> IO {network} {}
590sendBatchedGossip = \peerServers allEvents ->
588sendBatchedGossip = \peerServers allEvents ->
591 sendToEach peerServers (\server -> sendBatchedGossipToServer server allEvents)
589 sendToEach peerServers (\server -> sendBatchedGossipToServer server allEvents)
592
590
593−broadcastOfflineGossip = \t peerServers expiredLocal -> do
591+broadcastOfflineGossip = \t peerServers expiredLocal ->
592+ when (federationEnabled && count peerServers > 0 && count expiredLocal > 0) (do
594− let expiredEntries = mergeByKey (\a b -> a.pubkey == b.pubkey) [] (do
593+ let expiredEntries = mergeByKey (\a b -> a.pubkey == b.pubkey) [] (do
595− p <- expiredLocal
594+ p <- expiredLocal
596− let ttl = clamp offlineGossipMinTtl maxPresenceTtl ((p.expiresAt - p.timestamp) / 1000)
595+ let ttl = clamp offlineGossipMinTtl maxPresenceTtl ((p.expiresAt - p.timestamp) / 1000)
597− yield {pubkey: p.pubkey, ttl: ttl, expiresAt: p.expiresAt})
596+ yield {pubkey: p.pubkey, ttl: ttl, expiresAt: p.expiresAt})
598− when (federationEnabled && count expiredEntries > 0 && count peerServers > 0) (do
599 let gossipEvents = do
597 let gossipEvents = do
600 e <- expiredEntries
598 e <- expiredEntries
601 yield {eventType: "offline", pubkey: e.pubkey, server: serverHostN, timestamp: e.expiresAt, ttl: e.ttl}
599 yield {eventType: "offline", pubkey: e.pubkey, server: serverHostN, timestamp: e.expiresAt, ttl: e.ttl}
@@ -767,20 +765,18 @@ serve = \req -> case req of
767 then collectPeerServers updatedPresence (\p -> p.server)
765 then collectPeerServers updatedPresence (\p -> p.server)
768 else []
766 else []
769 yield {pr: pr, verify: VerifyOk {peerServers: peerServers}}
767 yield {pr: pr, verify: VerifyOk {peerServers: peerServers}}
770− fork (do
771− case result.verify of
768+ fork (case result.verify of
772− VerifyOk {peerServers} ->
769+ VerifyOk {peerServers} -> do
773− when federationEnabled
770+ when federationEnabled
774− (sendToEach peerServers (\server -> do
771+ (sendToEach peerServers (\server -> do
775− _ <- fetch (federationScheme ++ server) (RecvGossip {
772+ _ <- fetch (federationScheme ++ server) (RecvGossip {
776− authorization: "Bearer " ++ federationSecret,
773+ authorization: "Bearer " ++ federationSecret,
777− events: [{eventType: "online", pubkey: pubkey, server: serverHostN, timestamp: t, ttl: onlineGossipTtl}], hopsRemaining: 2, fromServer: serverHostN})
774+ events: [{eventType: "online", pubkey: pubkey, server: serverHostN, timestamp: t, ttl: onlineGossipTtl}], hopsRemaining: 2, fromServer: serverHostN})
778− yield {}))
775+ yield {}))
779− _ -> yield {}
776+ broadcastOfflineGossip t result.pr.peerServers
780− let expiredLocal = case result.verify of
781− VerifyOk _ -> do p <- result.pr.expiredLocal; where not (p.pubkey == pubkey); yield p
777+ (do p <- result.pr.expiredLocal; where not (p.pubkey == pubkey); yield p)
782− _ -> result.pr.expiredLocal
778+ _ ->
783− broadcastOfflineGossip t result.pr.peerServers expiredLocal)
779+ broadcastOfflineGossip t result.pr.peerServers result.pr.expiredLocal)
784 yield (case result.verify of
780 yield (case result.verify of
785 VerifyFailed _ -> respond {token: "", expiresAt: 0}
781 VerifyFailed _ -> respond {token: "", expiresAt: 0}
786 VerifyOk _ -> respond {token: token, expiresAt: expiry})
782 VerifyOk _ -> respond {token: token, expiresAt: expiry})
@@ -800,13 +796,14 @@ serve = \req -> case req of
800 SendMessage {to, encryptedBlob, timestamp, authorization, respond} -> do
796 SendMessage {to, encryptedBlob, timestamp, authorization, respond} -> do
801 let to = normalizeHex to
797 let to = normalizeHex to
802 let encryptedBlob = normalizeHex encryptedBlob
798 let encryptedBlob = normalizeHex encryptedBlob
799+ let token = extractToken authorization
803 t <- now
800 t <- now
804 msgId <- genToken
801 msgId <- genToken
805 let clampedTs = clampTimestamp timestamp t
802 let clampedTs = clampTimestamp timestamp t
806 result <- atomic do
803 result <- atomic do
807 pr <- maybePrune t
804 pr <- maybePrune t
808 sessions <- *sessions
805 sessions <- *sessions
809− let auth = authenticateSession sessions (extractToken authorization) t
806+ let auth = authenticateSession sessions token t
810 case auth of
807 case auth of
811 Unauthorized {} ->
808 Unauthorized {} ->
812 yield {pr: pr, send: SendUnauthorized {}}
809 yield {pr: pr, send: SendUnauthorized {}}
@@ -855,12 +852,13 @@ serve = \req -> case req of
855 SendDone {status} -> respond {status: status, messageId: msgId})
852 SendDone {status} -> respond {status: status, messageId: msgId})
856
853
857 AckMessages {messageIds, authorization, respond} -> do
854 AckMessages {messageIds, authorization, respond} -> do
855+ let token = extractToken authorization
858 t <- now
856 t <- now
859 let dedupedIds = dedupMessageIds messageIds
857 let dedupedIds = dedupMessageIds messageIds
860 result <- atomic do
858 result <- atomic do
861 pr <- maybePrune t
859 pr <- maybePrune t
862 sessions <- *sessions
860 sessions <- *sessions
863− let auth = authenticateSession sessions (extractToken authorization) t
861+ let auth = authenticateSession sessions token t
864 case auth of
862 case auth of
865 Unauthorized {} ->
863 Unauthorized {} ->
866 yield {pr: pr, ack: AckUnauthorized {}}
864 yield {pr: pr, ack: AckUnauthorized {}}
@@ -888,35 +886,36 @@ serve = \req -> case req of
888 yield {pr: pr, ack: AckDone {pendingAcks: ackedFederated, evictedEntries: evictedEntries}}
886 yield {pr: pr, ack: AckDone {pendingAcks: ackedFederated, evictedEntries: evictedEntries}}
889 fork (do
887 fork (do
890 case result.ack of
888 case result.ack of
891− AckDone {pendingAcks, evictedEntries} -> do
889+ AckDone {pendingAcks, evictedEntries} ->
890+ when federationEnabled (do
892− let originServers = collectPeerServers pendingAcks (\a -> a.originServer)
891+ let originServers = collectPeerServers pendingAcks (\a -> a.originServer)
893− when (count originServers > 0 && federationEnabled)
892+ when (count originServers > 0)
894− (sendAcksAndClean pendingAcks originServers)
893+ (sendAcksAndClean pendingAcks originServers)
895− let evictedToRetry = do
894+ let evictedToRetry = do
896− a <- evictedEntries
895+ a <- evictedEntries
897− where none pendingAcks (\a2 -> a2.messageId == a.messageId && a2.toKey == a.toKey)
896+ where none pendingAcks (\a2 -> a2.messageId == a.messageId && a2.toKey == a.toKey)
898− yield a
897+ yield a
899− when (count evictedToRetry > 0 && federationEnabled) (do
898+ when (count evictedToRetry > 0) (do
900− let evictedOriginServers = collectPeerServers evictedToRetry (\a -> a.originServer)
899+ let evictedOriginServers = collectPeerServers evictedToRetry (\a -> a.originServer)
901− sendToEach evictedOriginServers (\server -> do
900+ sendToEach evictedOriginServers (\server -> do
902− fetchResult <- fetch (federationScheme ++ server) (FederationAck {
901+ fetchResult <- fetch (federationScheme ++ server) (FederationAck {
903− authorization: "Bearer " ++ federationSecret,
902+ authorization: "Bearer " ++ federationSecret,
904− messageIds: (do a <- evictedToRetry; where a.originServer == server; yield a.messageId),
903+ messageIds: (do a <- evictedToRetry; where a.originServer == server; yield a.messageId),
905− originServer: server, fromServer: serverHostN})
904+ originServer: server, fromServer: serverHostN})
906− case fetchResult of
905+ case fetchResult of
907− Err _ -> do
906+ Err _ -> do
908− dropped <- atomic do
907+ dropped <- atomic do
909− ackedIds <- *ackedIds
908+ ackedIds <- *ackedIds
910− let reinsert = do a <- evictedToRetry; where a.originServer == server; yield a
909+ let reinsert = do a <- evictedToRetry; where a.originServer == server; yield a
911− let combined = union ackedIds reinsert
910+ let combined = union ackedIds reinsert
912− let excess = count combined - maxAckedIds
911+ let excess = count combined - maxAckedIds
913− let afterEviction = if excess <= 0 then combined
912+ let afterEviction = if excess <= 0 then combined
914− else evictOldestAckedIds combined excess
913+ else evictOldestAckedIds combined excess
915− set *ackedIds = afterEviction
914+ set *ackedIds = afterEviction
916− yield excess
915+ yield excess
917− when (dropped > 0)
916+ when (dropped > 0)
918− (println ("ackMessages: ackedIds at capacity (" ++ show maxAckedIds ++ "), " ++ show dropped ++ " ack entries permanently lost for " ++ server))
917+ (println ("ackMessages: ackedIds at capacity (" ++ show maxAckedIds ++ "), " ++ show dropped ++ " ack entries permanently lost for " ++ server))
919− _ -> yield {}))
918+ _ -> yield {})))
920 _ -> yield {}
919 _ -> yield {}
921 broadcastOfflineGossip t result.pr.peerServers result.pr.expiredLocal)
920 broadcastOfflineGossip t result.pr.peerServers result.pr.expiredLocal)
922 yield (case result.ack of
921 yield (case result.ack of
@@ -925,11 +924,12 @@ serve = \req -> case req of
925
924
926 LookupPresence {pubkey, authorization, respond} -> do
925 LookupPresence {pubkey, authorization, respond} -> do
927 let pubkey = normalizeHex pubkey
926 let pubkey = normalizeHex pubkey
927+ let token = extractToken authorization
928 t <- now
928 t <- now
929 result <- atomic do
929 result <- atomic do
930 pr <- maybePrune t
930 pr <- maybePrune t
931 sessions <- *sessions
931 sessions <- *sessions
932− let auth = authenticateSession sessions (extractToken authorization) t
932+ let auth = authenticateSession sessions token t
933 case auth of
933 case auth of
934 Unauthorized {} ->
934 Unauthorized {} ->
935 yield {pr: pr, lookup: LookupUnauthorized {}}
935 yield {pr: pr, lookup: LookupUnauthorized {}}
@@ -953,7 +953,7 @@ serve = \req -> case req of
953 RecvGossip {events, hopsRemaining, fromServer, authorization, respond} -> do
953 RecvGossip {events, hopsRemaining, fromServer, authorization, respond} -> do
954 t <- now
954 t <- now
955 let authorized = verifyFederationAuth authorization
955 let authorized = verifyFederationAuth authorization
956− let cappedHops = if hopsRemaining > 5 then 5 else hopsRemaining
956+ let cappedHops = clamp 0 5 hopsRemaining
957 let fromServer = normalizeServerName fromServer
957 let fromServer = normalizeServerName fromServer
958 if not authorized || cappedHops < 1 || fromServer == serverHostN || count events > maxGossipEvents
958 if not authorized || cappedHops < 1 || fromServer == serverHostN || count events > maxGossipEvents
959 then do
959 then do