on master help sign in
Simplify client app: remove URL from ConnStatus, use PollReceived record, extract error banner, disable URL edit while connected.
72c9efeiko
client/App.fs+26−35
@@ -49,7 +49,7 @@ module App =
49 type ConnStatus =
49 type ConnStatus =
50 | Offline
50 | Offline
51 | Connecting
51 | Connecting
52− | Online of url: string * token: string
52+ | Online of token: string
53
53
54 type AuthState =
54 type AuthState =
55 | NoIdentity
55 | NoIdentity
@@ -57,10 +57,12 @@ module App =
57
57
58 type IncomingMessage = { SenderPubkey: string; Message: ChatMessage }
58 type IncomingMessage = { SenderPubkey: string; Message: ChatMessage }
59
59
60+ type PollReceived = { Events: int; Messages: int; Errors: int; FirstError: string option }
61+
60 type PollStatus =
62 type PollStatus =
61 | Idle
63 | Idle
62 | Polling
64 | Polling
63− | Received of events: int * messages: int * errors: int * firstError: string option
65+ | Received of PollReceived
64 | PollError of string
66 | PollError of string
65
67
66 // ── Model ──
68 // ── Model ──
@@ -160,7 +162,7 @@ module App =
160
162
161 let private trySession model =
163 let private trySession model =
162 match model.Auth with
164 match model.Auth with
163− | Identified(id, Online(url, token)) -> Some { Url = url; Token = token; Identity = id }
165+ | Identified(id, Online token) -> Some { Url = model.ServerUrl; Token = token; Identity = id }
164 | _ -> None
166 | _ -> None
165
167
166 let private setConn status model =
168 let private setConn status model =
@@ -225,7 +227,7 @@ module App =
225 match model.Auth with
227 match model.Auth with
226 | Identified(id, Connecting) ->
228 | Identified(id, Connecting) ->
227 let session = { Url = model.ServerUrl; Token = token; Identity = id }
229 let session = { Url = model.ServerUrl; Token = token; Identity = id }
228− { model with Auth = Identified(id, Online(model.ServerUrl, token)); Page = Conversations; Error = None },
230+ { model with Auth = Identified(id, Online token); Page = Conversations; Error = None },
229 [ CmdPoll(session, model.PollCursor) ]
231 [ CmdPoll(session, model.PollCursor) ]
230 | _ -> model, []
232 | _ -> model, []
231
233
@@ -392,7 +394,7 @@ module App =
392 if not ackIds.IsEmpty then
394 if not ackIds.IsEmpty then
393 do! ackMessages session.Url session.Token ackIds
395 do! ackMessages session.Url session.Token ackIds
394
396
395− let status = Received(response.Events.Length, messages.Length, errors.Length, List.tryHead errors)
397+ let status = Received { Events = response.Events.Length; Messages = messages.Length; Errors = errors.Length; FirstError = List.tryHead errors }
396 return PollResult(messages, status, response.Cursor)
398 return PollResult(messages, status, response.Cursor)
397 with
399 with
398 | :? TimeoutException ->
400 | :? TimeoutException ->
@@ -429,11 +431,20 @@ module App =
429 let private formatPollStatus = function
431 let private formatPollStatus = function
430 | Idle -> ""
432 | Idle -> ""
431 | Polling -> "polling..."
433 | Polling -> "polling..."
432− | Received(evts, msgs, errs, firstErr) ->
434+ | Received r ->
433− $"evts:{evts} msgs:{msgs} errs:{errs}"
435+ $"evts:{r.Events} msgs:{r.Messages} errs:{r.Errors}"
434− + (match firstErr with Some e -> $" [{e}]" | None -> "")
436+ + (match r.FirstError with Some e -> $" [{e}]" | None -> "")
435 | PollError msg -> $"poll error: {msg}"
437 | PollError msg -> $"poll error: {msg}"
436
438
439+ let private viewErrorBanner (error: string option) =
440+ VStack(spacing = 4.) {
441+ match error with
442+ | Some err ->
443+ Label(err).textColor(Colors.Red).font(size = 12.)
444+ Button("Dismiss", DismissError)
445+ | None -> ()
446+ }
447+
437 let private viewSetup () =
448 let private viewSetup () =
438 ContentPage(
449 ContentPage(
439 (VStack(spacing = 24.) {
450 (VStack(spacing = 24.) {
@@ -471,16 +482,11 @@ module App =
471
482
472 Label("Server:")
483 Label("Server:")
473
484
474− Entry(model.ServerUrl, SetServerUrl)
475−
476− match model.Error with
485+ match conn with
477− | Some err ->
486+ | Online _ -> Label(model.ServerUrl).font(size = 14.)
478− Label(err)
487+ | _ -> Entry(model.ServerUrl, SetServerUrl)
479− .textColor(Colors.Red)
480− .font(size = 12.)
481
488
482− Button("Dismiss", DismissError)
489+ viewErrorBanner model.Error
483− | None -> ()
484
490
485 match conn with
491 match conn with
486 | Offline -> Button("Connect", DoConnect)
492 | Offline -> Button("Connect", DoConnect)
@@ -516,11 +522,7 @@ module App =
516 .font(size = 10.)
522 .font(size = 10.)
517 .textColor(Colors.DimGray)
523 .textColor(Colors.DimGray)
518
524
519− match model.Error with
525+ viewErrorBanner model.Error
520− | Some err ->
521− Label(err).textColor(Colors.Red).font(size = 12.)
522− Button("Dismiss", DismissError)
523− | None -> ()
524
526
525 if model.Contacts.IsEmpty then
527 if model.Contacts.IsEmpty then
526 Label("No contacts yet. Tap + to add one.")
528 Label("No contacts yet. Tap + to add one.")
@@ -554,14 +556,7 @@ module App =
554 .centerTextHorizontal()
556 .centerTextHorizontal()
555 }
557 }
556
558
557− match model.Error with
559+ viewErrorBanner model.Error
558− | Some err ->
559− Label(err)
560− .textColor(Colors.Red)
561− .font(size = 12.)
562−
563− Button("Dismiss", DismissError)
564− | None -> ()
565
560
566 if msgs.IsEmpty then
561 if msgs.IsEmpty then
567 Label("No messages yet")
562 Label("No messages yet")
@@ -593,11 +588,7 @@ module App =
593 .font(size = 20.)
588 .font(size = 20.)
594 }
589 }
595
590
596− match model.Error with
591+ viewErrorBanner model.Error
597− | Some err ->
598− Label(err).textColor(Colors.Red).font(size = 12.)
599− Button("Dismiss", DismissError)
600− | None -> ()
601
592
602 Label("Public Key:")
593 Label("Public Key:")
603 Entry(pubkey, SetContactPubkey)
594 Entry(pubkey, SetContactPubkey)
server.knot+13−13
@@ -184,6 +184,7 @@ whenOk = \result action -> case result of
184 Err _ -> yield {}
184 Err _ -> yield {}
185clamp = \lo hi x -> if x < lo then lo else if x > hi then hi else x
185clamp = \lo hi x -> if x < lo then lo else if x > hi then hi else x
186min = \a b -> if a < b then a else b
186min = \a b -> if a < b then a else b
187+max = \a b -> if a > b then a else b
187headOf = \items -> fold (\acc x -> case acc of
188headOf = \items -> fold (\acc x -> case acc of
188 Nothing {} -> Just {value: x}
189 Nothing {} -> Just {value: x}
189 _ -> acc) Nothing {} items
190 _ -> acc) Nothing {} items
@@ -397,9 +398,9 @@ boundedSmallest = \items n getKey ->
397 let newHeap = union acc.heap [a] in
398 let newHeap = union acc.heap [a] in
398 let newSize = count newHeap in
399 let newSize = count newHeap in
399 if newSize > acc.size
400 if newSize > acc.size
400− then {size: newSize, heap: newHeap, heapMax: case acc.heapMax of
401+ then {size: newSize, heap: newHeap, heapMax: Just {value: case acc.heapMax of
401− Nothing {} -> Just {value: k}
402+ Nothing {} -> k
402− Just {value: v} -> if k > v then Just {value: k} else acc.heapMax}
403+ Just {value: v} -> max k v}}
403 else acc
404 else acc
404 else case acc.heapMax of
405 else case acc.heapMax of
405 Nothing {} -> acc
406 Nothing {} -> acc
@@ -411,9 +412,9 @@ boundedSmallest = \items n getKey ->
411 then {done: True {}, items: r.items}
412 then {done: True {}, items: r.items}
412 else {done: r.done, items: union r.items [x]}) {done: False {}, items: []} acc.heap in
413 else {done: r.done, items: union r.items [x]}) {done: False {}, items: []} acc.heap in
413 let newHeap = union removed.items [a] in
414 let newHeap = union removed.items [a] in
414− let newMax = fold (\best x -> case best of
415+ let newMax = fold (\best x -> Just {value: case best of
415− Nothing {} -> Just {value: getKey x}
416+ Nothing {} -> getKey x
416− Just {value: v} -> if getKey x > v then Just {value: getKey x} else best) Nothing {} newHeap in
417+ Just {value: v} -> max (getKey x) v}) Nothing {} newHeap in
417 {size: acc.size, heap: newHeap, heapMax: newMax}
418 {size: acc.size, heap: newHeap, heapMax: newMax}
418 else acc) {size: 0, heap: [], heapMax: Nothing {}} items).heap
419 else acc) {size: 0, heap: [], heapMax: Nothing {}} items).heap
419
420
@@ -430,9 +431,9 @@ safeCompare = \a b ->
430 safeCompareLoop a b maxTokenLen 0 + (if a == "" then 1 else 0) + (if b == "" then 1 else 0) == 0
431 safeCompareLoop a b maxTokenLen 0 + (if a == "" then 1 else 0) + (if b == "" then 1 else 0) == 0
431
432
432evictOneOldest = \items getKey ->
433evictOneOldest = \items getKey ->
433− let oldest = fold (\acc x -> let k = getKey x in case acc of
434+ let oldest = fold (\acc x -> let k = getKey x in Just {value: case acc of
434− Nothing {} -> Just {value: k}
435+ Nothing {} -> k
435− Just {value: v} -> if k < v then Just {value: k} else acc) Nothing {} items in
436+ Just {value: v} -> min k v}) Nothing {} items in
436 case oldest of
437 case oldest of
437 Nothing {} -> items
438 Nothing {} -> items
438 Just {value: oldestKey} ->
439 Just {value: oldestKey} ->
@@ -706,8 +707,7 @@ pollOnce = \token cursor -> do
706 Authorized {pubkey} -> do
707 Authorized {pubkey} -> do
707 seqCounter <- *seqCounter
708 seqCounter <- *seqCounter
708 messages <- *messages
709 messages <- *messages
709− let cursorCeil = if seqCounter > t then seqCounter else t
710− let clampedCursor = clamp 0 cursorCeil cursor
710+ let clampedCursor = clamp 0 (max seqCounter t) cursor
711 let matchingMsgs = do
711 let matchingMsgs = do
712 m <- messages
712 m <- messages
713 where m.toKey == pubkey && m.receivedAt >= t - messageMaxAge && m.receivedAt > clampedCursor
713 where m.toKey == pubkey && m.receivedAt >= t - messageMaxAge && m.receivedAt > clampedCursor
@@ -719,7 +719,7 @@ pollOnce = \token cursor -> do
719 payload: {toKey: m.toKey, fromKey: m.fromKey,
719 payload: {toKey: m.toKey, fromKey: m.fromKey,
720 encryptedBlob: m.encryptedBlob,
720 encryptedBlob: m.encryptedBlob,
721 timestamp: m.timestamp}}
721 timestamp: m.timestamp}}
722− let lastReceivedAt = fold (\acc m -> if m.receivedAt > acc then m.receivedAt else acc) clampedCursor limitedMsgs
722+ let lastReceivedAt = fold (\acc m -> max acc m.receivedAt) clampedCursor limitedMsgs
723 yield {events: evts, cursor: lastReceivedAt, authorized: True {}}
723 yield {events: evts, cursor: lastReceivedAt, authorized: True {}}
724
724
725pollLoop = \token cursor deadline -> do
725pollLoop = \token cursor deadline -> do
@@ -761,7 +761,7 @@ serve = \req -> case req of
761 sessions <- *sessions
761 sessions <- *sessions
762 presence <- *presence
762 presence <- *presence
763 let hasMatch = any challenges (\c -> c.pubkey == pubkey && c.challenge == challenge && c.expiresAt > t)
763 let hasMatch = any challenges (\c -> c.pubkey == pubkey && c.challenge == challenge && c.expiresAt > t)
764− let sigValid = if hasMatch then verify (bytesFromHex pubkey) (textToBytes challenge) (bytesFromHex signature) else False {}
764+ let sigValid = hasMatch && verify (bytesFromHex pubkey) (textToBytes challenge) (bytesFromHex signature)
765 if not sigValid
765 if not sigValid
766 then yield {pr: pr, verified: False {}, peerServers: []}
766 then yield {pr: pr, verified: False {}, peerServers: []}
767 else do
767 else do