on master help sign in
Automatically reauthenticate when token expires during poll or send
commit4e3635c5529fdbbde831f427987e68f958513d78 authoriko <mail@iko.soy> dateSun 19 Apr 2026, 03:34 (6 months ago) parentf931cbc
3 files changed+21−3
client/ApiClient.fs+3−2 client/App.fs+17−1 client/AppTypes.fs+1
client/ApiClient.fs+3−2
@@ -11,6 +11,7 @@ module ApiClient =
1111
1212 exception ApiError of string
1313 exception ServerRejected of string
14+ exception Unauthorized
1415
1516 [<CLIMutable>]
1617 type EventPayload = { EncryptedBlob: string; Timestamp: int64 }
@@ -33,7 +34,7 @@ module ApiClient =
3334 type PollEvent = { Id: string; EventType: EventType; Payload: EventPayload }
3435
3536 [<CLIMutable>]
36− type PollResponse = { Cursor: int64; Events: PollEvent array }
37+ type PollResponse = { Cursor: int64; Events: PollEvent array; Authorized: bool }
3738
3839 /// Awaits a Task without converting TaskCanceledException to F# async
3940 /// cancellation (which bypasses try...with). Re-raises it as a regular exception.
@@ -99,7 +100,7 @@ module ApiClient =
99100 match doc.RootElement.GetProperty("status").GetString() with
100101 | "delivered" | "federated" | "queued" -> ()
101102 | "rejected" -> raise (ServerRejected "Message rejected by server")
102− | "unauthorized" -> raise (ServerRejected "Not authorized to deliver message")
103+ | "unauthorized" -> raise Unauthorized
103104 | s -> raise (ApiError $"Unexpected status: {s}")
104105 }
105106
client/App.fs+17−1
@@ -171,7 +171,7 @@ module App =
171171
172172 { model with
173173 Auth = Identified(id, Online token)
174− Page = Conversations
174+ Page = match model.Page with Setup -> Conversations | p -> p
175175 Error = None
176176 PollStatus = "polling..."
177177 FlushingOutbox = true },
@@ -185,6 +185,14 @@ module App =
185185 PollRetries = 0 },
186186 []
187187
188+ | TokenExpired ->
189+ match model.Auth with
190+ | Identified(id, Online _) ->
191+ let m = setConn Connecting model
192+ { m with FlushingOutbox = false; PollRetries = 0 },
193+ [ CmdConnect(model.ServerUrl, id) ]
194+ | _ -> model, []
195+
188196 | DoDisconnect ->
189197 { setConn Offline model with
190198 PollStatus = ""
@@ -482,6 +490,9 @@ module App =
482490 do! Store.dequeueOutbox item.Id
483491 dispatch (FlushResult true)
484492 with
493+ | Unauthorized ->
494+ log "outbox unauthorized, reauthenticating"
495+ dispatch TokenExpired
485496 | ServerRejected msg ->
486497 log $"outbox rejected, dropping: {msg}"
487498 do! Store.dequeueOutbox item.Id
@@ -503,6 +514,11 @@ module App =
503514 let! response = poll session.Url session.Token cursor
504515 log $"poll response: {response.Events.Length} events, cursor={response.Cursor}"
505516
517+ if not response.Authorized then
518+ log "poll unauthorized, reauthenticating"
519+ dispatch TokenExpired
520+ else
521+
506522 let ackIds = response.Events |> Array.map _.Id |> Array.toList
507523
508524 let results =
client/AppTypes.fs+1
@@ -84,6 +84,7 @@ module AppTypes =
8484 | StartFlush
8585 | FlushResult of sent: bool
8686 | Search of string
87+ | TokenExpired
8788 | KeyboardHeightChanged of float
8889
8990 // ── CmdMsg ──