on master help sign in
Improve type safety and simplify server.knot: convert PollEvent.eventType to DU, remove redundant isGossipOffline helper, simplify LookupPresence server selection and trustedOffline derivation
commit6b1d74aecd0ed0fb71c97a95acff19b5fb9bf149 authoriko <mail@iko.soy> dateFri 10 Apr 2026, 19:19 (6 months ago) parent32ef8d2
12 files changed+43−40
client/App.fs+17−14 client/Phonemic.fs+4−12 client/bin/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.dll client/bin/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.pdb client/obj/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.AssemblyInfo.fs+1−1 client/obj/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.AssemblyInfoInputs.cache+1−1 client/obj/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.dll client/obj/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.pdb client/obj/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.sourcelink.json+1−1 client/obj/Debug/net10.0-maccatalyst/maccatalyst-arm64/ref/Skrepka.dll client/obj/Debug/net10.0-maccatalyst/maccatalyst-arm64/refint/Skrepka.dll server.knot+19−11
client/App.fs+17−14
@@ -136,6 +136,7 @@ module App =
136136 let private truncKey (hex: string) = hexToOb hex |> truncOb
137137
138138 let private tryParsePubkey (input: string) =
139+ let input = input.Trim()
139140 (Phonemic.fromOb input |> Option.orElseWith (fun () -> Crypto.tryFromHex input))
140141 |> Option.filter (fun bytes -> bytes.Length = 32)
141142
@@ -375,14 +376,16 @@ module App =
375376 asyncCmd (async {
376377 try
377378 let! response = poll session.Url session.Token cursor
378− let messages, ackIds, errors =
379+ let ackIds = response.Events |> Array.map _.Id |> Array.toList
380+
381+ let messages, errors =
379382 Array.foldBack
380− (fun (evt: PollEvent) (msgs, acks, errs) ->
383+ (fun (evt: PollEvent) (msgs, errs) ->
381384 match decryptEvent session.Identity.PrivKey evt.Payload with
382− | Ok msg -> (msg :: msgs, evt.Id :: acks, errs)
383− | Error err -> (msgs, acks, err :: errs))
385+ | Ok msg -> (msg :: msgs, errs)
386+ | Error err -> (msgs, err :: errs))
384387 response.Events
385− ([], [], [])
388+ ([], [])
386389
387390 if not ackIds.IsEmpty then
388391 do! ackMessages session.Url session.Token ackIds
@@ -565,15 +568,15 @@ module App =
565568 .font(size = 11.)
566569 | None -> ()
567570
568− let allMessages =
569− msgs
570− |> List.map (fun m ->
571− if m.IsOutgoing then $"You: {m.Body}" else m.Body)
572− |> String.concat "\n"
573−
574− Label(if allMessages = "" then "No messages yet" else allMessages)
575− .padding(8.)
576− .font(size = 14.)
571+ if msgs.IsEmpty then
572+ Label("No messages yet")
573+ .padding(8.)
574+ .font(size = 14.)
575+ else
576+ for m in msgs do
577+ Label(if m.IsOutgoing then $"You: {m.Body}" else m.Body)
578+ .padding(8.)
579+ .font(size = 14.)
577580
578581 HStack(spacing = 8.) {
579582 Entry(model.Compose, SetCompose)
client/Phonemic.fs+4−12
@@ -82,18 +82,10 @@ module Phonemic =
8282 suffixes |> Array.mapi (fun i s -> s, byte i) |> Map.ofArray
8383
8484 let toOb (bytes: byte[]) : string =
85− let sb = System.Text.StringBuilder()
86−
87− for i in 0..2 .. bytes.Length - 1 do
88− let pair = i / 2
89−
90− if pair > 0 then
91− sb.Append('-') |> ignore
92−
93− sb.Append(prefixes.[int bytes.[i]]).Append(suffixes.[int bytes.[i + 1]])
94− |> ignore
95−
96− sb.ToString()
85+ bytes
86+ |> Array.chunkBySize 2
87+ |> Array.map (fun pair -> $"{prefixes.[int pair.[0]]}{suffixes.[int pair.[1]]}")
88+ |> String.concat "-"
9789
9890 let fromOb (s: string) : byte[] option =
9991 let pairs = s.Split('-', StringSplitOptions.RemoveEmptyEntries)
client/bin/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.dll

Binary file not shown.

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

Binary file not shown.

client/obj/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.AssemblyInfo.fs+1−1
@@ -10,7 +10,7 @@ open System.Reflection
1010[<assembly: System.Reflection.AssemblyCompanyAttribute("Skrepka")>]
1111[<assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")>]
1212[<assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")>]
13−[<assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0+e7abeea926015538a30f3346744ca1635404a646")>]
13+[<assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0+32ef8d27d94c098194e967c81ad2bc33c2c7e1a4")>]
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−ac592265f098d871e6f04a2ad8e3355d036bd09f4c75fa6fc8a66820739b5515
1+421534857e1be1cf4a3871eb3b32dc004774e6483af04a426ef4c607c9a30ce5
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/e7abeea926015538a30f3346744ca1635404a646/*"}}
1+{"documents":{"/Users/iko/Developer/skrepka/*":"https://raw.githubusercontent.com/ilyakooo0/skrepka/32ef8d27d94c098194e967c81ad2bc33c2c7e1a4/*"}}
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+19−11
@@ -48,7 +48,15 @@ federationEnabled = federationSecret != ""
4848-- Types
4949-- ============================================
5050
51−type PollEvent = {id: MessageIdHex, eventType: Text, payload: {encryptedBlob: BlobHex, fromKey: PubkeyHex, timestamp: Timestamp, toKey: PubkeyHex}}
51+type PollEventType = PollMessage {}
52+
53+impl ToJSON PollEventType where
54+ toJson _ = toJson "message"
55+
56+impl FromJSON PollEventType where
57+ parseJson _ = PollMessage {}
58+
59+type PollEvent = {id: MessageIdHex, eventType: PollEventType, payload: {encryptedBlob: BlobHex, fromKey: PubkeyHex, timestamp: Timestamp, toKey: PubkeyHex}}
5260
5361type GossipEventType = GossipOnline {} | GossipOffline {}
5462
@@ -63,7 +71,6 @@ impl FromJSON GossipEventType where
6371 if s == "online" then GossipOnline {} else GossipOffline {}
6472
6573isGossipOnline = \et -> case et of GossipOnline {} -> True {}; _ -> False {}
66−isGossipOffline = \et -> case et of GossipOffline {} -> True {}; _ -> False {}
6774
6875type GossipEvent = {eventType: GossipEventType, pubkey: PubkeyHex, server: ServerName, timestamp: Timestamp, ttl: Int<S>}
6976
@@ -715,7 +722,7 @@ pollOnce = \token cursor -> do
715722 let limitedMsgs = boundedSmallest matchingMsgs maxPollEvents (\m -> m.receivedAt)
716723 let evts = do
717724 m <- limitedMsgs
718− yield {id: m.messageId, eventType: "message",
725+ yield {id: m.messageId, eventType: PollMessage {},
719726 payload: {toKey: m.toKey, fromKey: m.fromKey,
720727 encryptedBlob: m.encryptedBlob,
721728 timestamp: m.timestamp}}
@@ -906,11 +913,12 @@ serve = \req -> case req of
906913 p <- presence
907914 where p.pubkey == pubkey && p.expiresAt > t
908915 yield p
909− let foundServer = (fold (\acc p ->
910− if acc.server == serverHostN then acc
911− else if p.server == serverHostN then {server: serverHostN, best: p.expiresAt}
912− else if p.expiresAt > acc.best then {server: p.server, best: p.expiresAt}
913− else acc) {server: "", best: 0} valid).server
916+ let localPresent = any valid (\p -> p.server == serverHostN)
917+ let bestRemote = (fold (\acc p ->
918+ if p.server != serverHostN && p.expiresAt > acc.best
919+ then {server: p.server, best: p.expiresAt}
920+ else acc) {server: "", best: 0} valid).server
921+ let foundServer = if localPresent then serverHostN else bestRemote
914922 yield {pr: pr, authorized: True {}, foundServer: foundServer}
915923 forkBroadcastOffline t result.pr
916924 yield (respond {online: result.foundServer != "", server: result.foundServer, authorized: result.authorized})
@@ -926,7 +934,7 @@ serve = \req -> case req of
926934 else do
927935 let allValidEntries = validGossipEvents events t
928936 let onlineEntries = do e <- allValidEntries; where isGossipOnline e.eventType; yield e
929− let offlineEntries = do e <- allValidEntries; where isGossipOffline e.eventType; yield e
937+ let offlineEntries = do e <- allValidEntries; where not (isGossipOnline e.eventType); yield e
930938 let filteredOnline = do
931939 e <- onlineEntries
932940 where none offlineEntries (\o -> o.server == fromServer && samePubkeyServer o e && o.timestamp > e.timestamp)
@@ -936,8 +944,8 @@ serve = \req -> case req of
936944 where none onlineEntries (\o -> samePubkeyServer o e && o.timestamp > e.timestamp)
937945 yield e
938946 let trustedOffline = do
939− e <- offlineEntries
940− where e.server == fromServer
947+ e <- allValidEntries
948+ where not (isGossipOnline e.eventType) && e.server == fromServer
941949 yield e
942950 let deduped = mergeByPubkeyServer [] filteredOnline
943951 gossipResult <- atomic do