on master help sign in
Simplify code and improve type safety: JsonNumberHandling, resource disposal, explicit MessageStatus patterns, use Option.ofObj, factor authenticateRequest
commit5889a08b3e45da71bce41ed6d0640f225108f732 authoriko <mail@iko.soy> dateFri 10 Apr 2026, 13:08 (6 months ago) parent6357861
4 files changed+25−49
client/ApiClient.fs+10−29 client/App.fs+2−3 client/Store.fs+3−3 server.knot+10−14
client/ApiClient.fs+10−29
@@ -39,14 +39,6 @@ module ApiClient =
3939 else ok t.Result)
4040 |> ignore)
4141
42− type private FlexibleInt64Converter() =
43− inherit JsonConverter<int64>()
44− override _.Read(reader, _, _) =
45− match reader.TokenType with
46− | JsonTokenType.String -> Int64.Parse(reader.GetString())
47− | _ -> reader.GetInt64()
48− override _.Write(writer, value, _) = writer.WriteNumberValue(value)
49−
5042 type private MessageStatusConverter() =
5143 inherit JsonConverter<MessageStatus>()
5244 override _.Read(reader, _, _) =
@@ -67,8 +59,7 @@ module ApiClient =
6759 | Unauthorized -> "unauthorized")
6860
6961 let private jsonOpts =
70− let opts = JsonSerializerOptions(PropertyNameCaseInsensitive = true)
71− opts.Converters.Add(FlexibleInt64Converter())
62+ let opts = JsonSerializerOptions(PropertyNameCaseInsensitive = true, NumberHandling = JsonNumberHandling.AllowReadingFromString)
7263 opts.Converters.Add(MessageStatusConverter())
7364 opts
7465
@@ -84,34 +75,24 @@ module ApiClient =
8475
8576 let private sendRequest (httpClient: HttpClient) (url: string) (body: string) (token: string option) =
8677 async {
87− let request = new HttpRequestMessage(HttpMethod.Post, url)
78+ use request = new HttpRequestMessage(HttpMethod.Post, url)
8879 request.Content <- new StringContent(body, Encoding.UTF8, "application/json")
89−
90− token
91− |> Option.iter (fun t -> request.Headers.Add("Authorization", $"Bearer {t}"))
92−
93− let! response = httpClient.SendAsync(request) |> awaitTask
80+ token |> Option.iter (fun t -> request.Headers.Add("Authorization", $"Bearer {t}"))
81+ use! response = httpClient.SendAsync(request) |> awaitTask
9482 let! text = response.Content.ReadAsStringAsync() |> awaitTask
95− let doc = JsonDocument.Parse(text)
96−
83+ use doc = JsonDocument.Parse(text)
9784 match doc.RootElement.TryGetProperty("error") with
98− | true, err ->
99− let msg = err.GetString()
100− doc.Dispose()
101− return failwith $"{url}: {msg}"
102− | _ -> return doc
85+ | true, err -> return failwith $"{url}: {err.GetString()}"
86+ | _ -> return text
10387 }
10488
10589 let private post httpClient url body token =
106− async {
107− use! _doc = sendRequest httpClient url body token
108− return ()
109− }
90+ sendRequest httpClient url body token |> Async.Ignore
11091
11192 let private postJson<'T> httpClient url body token =
11293 async {
113− use! doc = sendRequest httpClient url body token
114− return doc.RootElement.Deserialize<'T>(jsonOpts)
94+ let! text = sendRequest httpClient url body token
95+ return JsonSerializer.Deserialize<'T>(text, jsonOpts)
11596 }
11697
11798 let authenticate (serverUrl: string) (identity: Crypto.Identity) =
client/App.fs+2−3
@@ -201,11 +201,11 @@ module App =
201201 model', CmdSend(session, pk, compose, id) :: saveCmd model'
202202 | _ -> model, []
203203
204+ | Sent(Ok { Status = ApiClient.Delivered | ApiClient.Federated | ApiClient.Queued }) -> model, []
204205 | Sent(Ok { Status = ApiClient.Rejected }) ->
205206 { model with Error = Some "Message rejected by server" }, []
206207 | Sent(Ok { Status = ApiClient.Unauthorized }) ->
207208 { model with Error = Some "Not authorized to deliver message" }, []
208− | Sent(Ok _) -> model, []
209209 | Sent(Error err) -> { model with Error = Some $"Send failed: {err}" }, []
210210
211211 | PollResult(incoming, status, newCursor) ->
@@ -493,8 +493,7 @@ module App =
493493 model.Messages
494494 |> Map.tryFind c.Pubkey
495495 |> Option.bind List.tryLast
496− |> Option.map _.Body
497− |> Option.map (fun b -> if b.Length > 40 then b.[..39] + "..." else b)
496+ |> Option.map (fun m -> if m.Body.Length > 40 then m.Body.[..39] + "..." else m.Body)
498497 |> Option.defaultValue ""
499498
500499 Button($"{c.Nickname}\n{preview}", Nav(Chat(c.Pubkey, "")))
client/Store.fs+3−3
@@ -71,9 +71,9 @@ module Store =
7171 use db = openDb ()
7272 let settings = db.GetCollection<SettingsDoc>("settings").FindById(BsonValue "settings")
7373
74− match box settings with
75− | null -> None
76− | _ ->
74+ match settings |> Option.ofObj with
75+ | None -> None
76+ | Some settings ->
7777 let contacts = db.GetCollection<Contact>("contacts").FindAll() |> Seq.toList
7878
7979 let messages =
server.knot+10−14
@@ -69,10 +69,10 @@ type PresenceEntry = {pubkey: PubkeyHex, server: Text, expiresAt: Timestamp, tim
6969type ForwardEntry = {messageId: MessageIdHex, toServer: ServerName, createdAt: Timestamp, toKey: PubkeyHex, fromKey: PubkeyHex, encryptedBlob: BlobHex, timestamp: Timestamp}
7070type AckedEntry = {messageId: MessageIdHex, toKey: PubkeyHex, ackedAt: Timestamp, originServer: ServerName}
7171type AuthResult = Authorized {pubkey: PubkeyHex} | Unauthorized {}
72−type PruneResult = {expiredLocal: [PresenceEntry], peerServers: [Text]}
72+type PruneResult = {expiredLocal: [PresenceEntry], peerServers: [ServerName]}
7373type PollResult = PollUnauthorized {} | PollAuthorized {events: [PollEvent], cursor: Timestamp}
74−type VerifyResult = VerifyFailed {} | VerifyOk {peerServers: [Text]}
75−type SendResult = SendUnauthorized {} | SendRejected {} | SendDone {status: MessageStatus, remoteServers: [Text], fromKey: PubkeyHex}
74+type VerifyResult = VerifyFailed {} | VerifyOk {peerServers: [ServerName]}
75+type SendResult = SendUnauthorized {} | SendRejected {} | SendDone {status: MessageStatus, remoteServers: [ServerName], fromKey: PubkeyHex}
7676type AckResult = AckUnauthorized {} | AckDone {pendingAcks: [AckedEntry], evictedEntries: [AckedEntry]}
7777type LookupResult = LookupUnauthorized {} | LookupDone {foundServer: OptionalServerName}
7878
@@ -371,6 +371,9 @@ authenticateSession = \sessions token t ->
371371 Nothing {} -> Unauthorized {}
372372 Just {value: s} -> Authorized {pubkey: s.pubkey}
373373
374+authenticateRequest = \sessions authorization t ->
375+ authenticateSession sessions (extractToken authorization) t
376+
374377extractToken = \authHeader ->
375378 let token = drop 7 authHeader in
376379 if toLower (take 7 authHeader) == "bearer " && not (tooLong token maxTokenLen)
@@ -771,11 +774,7 @@ serve = \req -> case req of
771774 case result.verify of
772775 VerifyOk {peerServers} ->
773776 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 {}))
777+ (sendBatchedGossip peerServers [{eventType: "online", pubkey: pubkey, server: serverHostN, timestamp: t, ttl: onlineGossipTtl}] 2)
779778 _ -> yield {}
780779 broadcastOfflineGossip t result.pr.peerServers expiredLocal)
781780 yield (case result.verify of
@@ -797,14 +796,13 @@ serve = \req -> case req of
797796 SendMessage {to, encryptedBlob, timestamp, authorization, respond} -> do
798797 let to = normalizeHex to
799798 let encryptedBlob = normalizeHex encryptedBlob
800− let token = extractToken authorization
801799 t <- now
802800 msgId <- genToken
803801 let clampedTs = clampTimestamp timestamp t
804802 result <- atomic do
805803 pr <- maybePrune t
806804 sessions <- *sessions
807− let auth = authenticateSession sessions token t
805+ let auth = authenticateRequest sessions authorization t
808806 case auth of
809807 Unauthorized {} ->
810808 yield {pr: pr, send: SendUnauthorized {}}
@@ -853,13 +851,12 @@ serve = \req -> case req of
853851 SendDone {status} -> respond {status: status, messageId: msgId})
854852
855853 AckMessages {messageIds, authorization, respond} -> do
856− let token = extractToken authorization
857854 t <- now
858855 let dedupedIds = dedupMessageIds messageIds
859856 result <- atomic do
860857 pr <- maybePrune t
861858 sessions <- *sessions
862− let auth = authenticateSession sessions token t
859+ let auth = authenticateRequest sessions authorization t
863860 case auth of
864861 Unauthorized {} ->
865862 yield {pr: pr, ack: AckUnauthorized {}}
@@ -921,12 +918,11 @@ serve = \req -> case req of
921918
922919 LookupPresence {pubkey, authorization, respond} -> do
923920 let pubkey = normalizeHex pubkey
924− let token = extractToken authorization
925921 t <- now
926922 result <- atomic do
927923 pr <- maybePrune t
928924 sessions <- *sessions
929− let auth = authenticateSession sessions token t
925+ let auth = authenticateRequest sessions authorization t
930926 case auth of
931927 Unauthorized {} ->
932928 yield {pr: pr, lookup: LookupUnauthorized {}}