on master help sign in
Tighten is172Private validation; skip safeCompare for expired sessions; enhance delivery animation
commitbcbb696cc07cdc6554c6e38c09f554be1d4f6ead authoriko <mail@iko.soy> dateWed 22 Apr 2026, 11:13 (6 months ago) parent0d03f1b
2 files changed+49−16
client/ViewChat.fs+47−15 server.knot+2−1
client/ViewChat.fs+47−15
@@ -55,8 +55,36 @@ module ViewChat =
5555 | None -> ()
5656 , Avalonia.Threading.DispatcherPriority.Background) |> ignore
5757
58− let private strokeTransition =
59− DoubleTransition(Avalonia.Controls.Shapes.Shape.StrokeThicknessProperty, System.TimeSpan.FromMilliseconds 800.)
58+ let private ensureSolidifyStyle =
59+ lazy
60+ (let s =
61+ Avalonia.Styling.Style(fun sel ->
62+ sel.OfType<Avalonia.Controls.Shapes.Rectangle>().Class("solidifying"))
63+
64+ let anim = Avalonia.Animation.Animation()
65+ anim.Duration <- System.TimeSpan.FromMilliseconds 600.
66+ anim.IterationCount <- Avalonia.Animation.IterationCount(1UL)
67+ anim.FillMode <- Avalonia.Animation.FillMode.Forward
68+
69+ for i in 0..10 do
70+ let t = float i / 10.
71+ let dash = 4. + 2. * t
72+ let gap = 2. - 2. * t
73+ let kf = Avalonia.Animation.KeyFrame()
74+ kf.Cue <- Avalonia.Animation.Cue(float32 t)
75+
76+ kf.Setters.Add(
77+ Avalonia.Styling.Setter(
78+ Avalonia.Controls.Shapes.Shape.StrokeDashArrayProperty,
79+ box (Avalonia.Collections.AvaloniaList<float>([| dash; gap |]))))
80+
81+ kf.Setters.Add(
82+ Avalonia.Styling.Setter(Avalonia.Controls.Shapes.Shape.StrokeDashOffsetProperty, box 0.))
83+
84+ anim.Children.Add(kf)
85+
86+ s.Animations.Add(anim)
87+ Avalonia.Application.Current.Styles.Add(s))
6088
6189 let private messageItem m =
6290 let align, cols, col =
@@ -76,21 +104,25 @@ module ViewChat =
76104
77105 match m.Direction with
78106 | Outgoing status ->
79− ensureMarchingAntsStyle.Force()
80107 let isSent = status = DeliveryStatus.Sent
81108
82− Rectangle()
83− .stroke(Avalonia.Media.Colors.Black)
84− .strokeThickness(4.)
85− .strokeDashArray([ 4.; 2. ])
86− .isHitTestVisible(false)
87− .classes("marching")
88−
89− Rectangle()
90− .stroke(Avalonia.Media.Colors.Black)
91− .strokeThickness(if isSent then 0. else 4.)
92− .isHitTestVisible(false)
93− .transition (strokeTransition)
109+ if isSent then
110+ ensureMarchingAntsStyle.Force()
111+
112+ Rectangle()
113+ .stroke(Avalonia.Media.Colors.Black)
114+ .strokeThickness(4.)
115+ .strokeDashArray([ 4.; 2. ])
116+ .isHitTestVisible(false)
117+ .classes("marching")
118+ else
119+ ensureSolidifyStyle.Force()
120+
121+ Rectangle()
122+ .stroke(Avalonia.Media.Colors.Black)
123+ .strokeThickness(4.)
124+ .isHitTestVisible(false)
125+ .classes("solidifying")
94126 | Incoming ->
95127 Rectangle()
96128 .stroke(Avalonia.Media.Colors.Black)
server.knot+2−1
@@ -340,6 +340,7 @@ is172Private = \sn ->
340340 && let d1 = take 1 (drop 4 sn) in
341341 let d2 = take 1 (drop 5 sn) in
342342 take 1 (drop 6 sn) == "."
343+ && isDigitChar (take 1 (drop 7 sn))
343344 && ((d1 == "1" && contains d2 "6789")
344345 || (d1 == "2" && isDigitChar d2)
345346 || (d1 == "3" && contains d2 "01"))
@@ -395,7 +396,7 @@ isBadServerName = \sn ->
395396
396397authenticateSession : [Session] -> Text -> Timestamp -> AuthResult
397398authenticateSession = \sessions token t ->
398− case headOf (do s <- sessions; where safeCompare s.token token && s.expiresAt > t; yield s) of
399+ case headOf (do s <- sessions; where s.expiresAt > t && safeCompare s.token token; yield s) of
399400 Nothing {} -> Unauthorized {}
400401 Just {value: s} -> Authorized {pubkey: s.pubkey}
401402