on master help sign in
Fix timeout handling with custom task awaiter and standardize timestamps to milliseconds
commit9d5373ae9d892e1bd4996b4a6b9ff04e8a52c459 authoriko <mail@iko.soy> dateThu 9 Apr 2026, 17:17 (6 months ago) parentac8c87f
31 files changed+60−56
client/ApiClient.fs+15−4 client/App.fs+11−18 client/bin/Debug/net10.0-ios/iossimulator-arm64/Skrepka.app/Skrepka.dll client/bin/Debug/net10.0-ios/iossimulator-arm64/Skrepka.app/Skrepka.pdb client/bin/Debug/net10.0-ios/iossimulator-arm64/Skrepka.app/_CodeSignature/CodeResources+4−4 client/bin/Debug/net10.0-ios/iossimulator-arm64/Skrepka.dll client/bin/Debug/net10.0-ios/iossimulator-arm64/Skrepka.pdb client/bin/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.app/Contents/MonoBundle/Skrepka.dll client/bin/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.app/Contents/MonoBundle/Skrepka.pdb client/bin/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.app/Contents/_CodeSignature/CodeResources+2−2 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-ios/iossimulator-arm64/Skrepka.AssemblyInfo.fs+1−1 client/obj/Debug/net10.0-ios/iossimulator-arm64/Skrepka.AssemblyInfoInputs.cache+1−1 client/obj/Debug/net10.0-ios/iossimulator-arm64/Skrepka.dll client/obj/Debug/net10.0-ios/iossimulator-arm64/Skrepka.pdb client/obj/Debug/net10.0-ios/iossimulator-arm64/Skrepka.sourcelink.json+1−1 client/obj/Debug/net10.0-ios/iossimulator-arm64/linked/Skrepka.dll client/obj/Debug/net10.0-ios/iossimulator-arm64/linked/Skrepka.pdb client/obj/Debug/net10.0-ios/iossimulator-arm64/ref/Skrepka.dll client/obj/Debug/net10.0-ios/iossimulator-arm64/refint/Skrepka.dll 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/linked/Skrepka.dll client/obj/Debug/net10.0-maccatalyst/maccatalyst-arm64/linked/Skrepka.pdb client/obj/Debug/net10.0-maccatalyst/maccatalyst-arm64/ref/Skrepka.dll client/obj/Debug/net10.0-maccatalyst/maccatalyst-arm64/refint/Skrepka.dll server.knot+22−22
client/ApiClient.fs+15−4
@@ -4,9 +4,20 @@ open System
44open System.Net.Http
55open System.Text
66open System.Text.Json
7+open System.Threading.Tasks
78
89module ApiClient =
910
11+ /// Awaits a Task without converting TaskCanceledException to F# async
12+ /// cancellation (which bypasses try...with). Re-raises it as a regular exception.
13+ let private awaitTask (t: Task<'T>) : Async<'T> =
14+ Async.FromContinuations(fun (ok, err, _cancel) ->
15+ t.ContinueWith(fun (t: Task<'T>) ->
16+ if t.IsFaulted then err t.Exception.InnerException
17+ elif t.IsCanceled then err (TimeoutException("Request timed out"))
18+ else ok t.Result)
19+ |> ignore)
20+
1021 let private client =
1122 let c = new HttpClient()
1223 c.Timeout <- TimeSpan.FromSeconds(60.)
@@ -87,13 +98,13 @@ module ApiClient =
8798 request.Content <- new StringContent($"""{{"cursor":{cursor},"timeout":30000}}""", Encoding.UTF8, "application/json")
8899 request.Headers.Add("Authorization", $"Bearer {token}")
89100
90− let! response = pollClient.SendAsync(request) |> Async.AwaitTask
91− let! text = response.Content.ReadAsStringAsync() |> Async.AwaitTask
101+ let! response = pollClient.SendAsync(request) |> awaitTask
102+ let! text = response.Content.ReadAsStringAsync() |> awaitTask
92103 let doc = JsonDocument.Parse(text)
93104 let root = expectObject doc "/poll"
94− let cursor = root.GetProperty("cursor").GetUInt64
105+ let cursor = root.GetProperty("cursor").GetUInt64()
95106
96− let events= root.GetProperty("events")
107+ let events = root.GetProperty("events")
97108
98109 let events =
99110 [ for e in events.EnumerateArray() do
client/App.fs+11−18
@@ -48,8 +48,7 @@ module App =
4848 NewNickname: string
4949 Error: string option
5050 PollCursor: uint64
51− LastSendStatus: string
52− Events: list<JsonElement> }
51+ LastSendStatus: string }
5352
5453 // ── Msg ──
5554
@@ -69,7 +68,7 @@ module App =
6968 | SetNewNickname of string
7069 | DoSaveContact
7170 | DoDeleteContact of string
72− | PollResult of (string * string * uint64 * string) list * string * uint64 * list<JsonElement>
71+ | PollResult of (string * string * int64 * string) list * string * uint64
7372 | DoPoll
7473 | CopyPubKey
7574 | DismissError
@@ -117,8 +116,7 @@ module App =
117116 NewNickname = ""
118117 Error = None
119118 PollCursor = 0UL
120− LastSendStatus = ""
121− Events = [] },
119+ LastSendStatus = "" },
122120 []
123121
124122 // ── Update ──
@@ -176,7 +174,7 @@ module App =
176174
177175 | Sent(_, status) -> { model with LastSendStatus = status }, []
178176
179− | PollResult(incoming, status, newCursor, events) ->
177+ | PollResult(incoming, status, newCursor) ->
180178 let model' =
181179 incoming
182180 |> List.fold
@@ -201,7 +199,7 @@ module App =
201199 { m with
202200 Contacts = contacts
203201 Messages = m.Messages |> Map.add fromPk (msgs @ [ newMsg ]) })
204− { model with PollStatus = status; PollCursor = newCursor; Events = model.Events @ events }
202+ { model with PollStatus = status; PollCursor = newCursor }
205203
206204 match model'.Conn with
207205 | Online token -> model', [ CmdPoll(model'.ServerUrl, token, model'.PrivKey.Value, model'.PollCursor) ]
@@ -315,9 +313,9 @@ module App =
315313 let tsEl = payload.GetProperty("timestamp")
316314 let ts =
317315 if tsEl.ValueKind = JsonValueKind.String then
318− UInt64.Parse(tsEl.GetString())
316+ Int64.Parse(tsEl.GetString())
319317 else
320− tsEl.GetUInt64()
318+ tsEl.GetInt64()
321319 let blob = Crypto.fromHex blobHex
322320
323321 match Crypto.decrypt privKey blob with
@@ -345,15 +343,14 @@ module App =
345343 $"[{now}] evts:{events.Length} msgs:{messages.Count} errs:{errors.Count}"
346344 + (if errors.Count > 0 then $" [{errors.[0]}]" else "")
347345
348− return PollResult(Seq.toList messages, status, newCursor, events |> List.map (fun (_, _, x) -> x))
346+ return PollResult(Seq.toList messages, status, newCursor)
349347 with
350− | :? System.Threading.Tasks.TaskCanceledException ->
351− // Normal timeout from long poll — just retry
352− return PollResult([], "polling...", cursor, [])
348+ | :? TimeoutException ->
349+ return PollResult([], "polling...", cursor)
353350 | ex ->
354351 let now = DateTimeOffset.UtcNow.ToString("HH:mm:ss")
355352 do! Async.Sleep 3000
356− return PollResult([], $"[{now}] poll error: {ex.Message}", cursor, [])
353+ return PollResult([], $"[{now}] poll error: {ex.Message}", cursor)
357354 })
358355
359356 | CmdCopyToClipboard text ->
@@ -451,10 +448,6 @@ module App =
451448 Label($"poll: {model.PollStatus}")
452449 .font(size = 10.)
453450 .textColor(Colors.DimGray)
454−
455− Label($"Events: {model.Events}")
456− .font(10)
457− .textColor(Colors.DimGray)
458451
459452 if contactNames <> "" then
460453 Label($"names: {contactNames}")
client/bin/Debug/net10.0-ios/iossimulator-arm64/Skrepka.app/Skrepka.dll

Binary file not shown.

client/bin/Debug/net10.0-ios/iossimulator-arm64/Skrepka.app/Skrepka.pdb

Binary file not shown.

client/bin/Debug/net10.0-ios/iossimulator-arm64/Skrepka.app/_CodeSignature/CodeResources+4−4
@@ -162,11 +162,11 @@
162162 </data>
163163 <key>Skrepka.dll</key>
164164 <data>
165− di0VTroRrk0Osy30exH9yUkakk8=
165+ r/iZ42W7ZFINtqwUABz6aXZb5Pk=
166166 </data>
167167 <key>Skrepka.pdb</key>
168168 <data>
169− /Vq3uwq4IB+ENxjl99BAiVqJSyI=
169+ zasvE0a+Wo6/nzeFiWsUuXJ8xvk=
170170 </data>
171171 <key>Sodium.Core.dll</key>
172172 <data>
@@ -1382,14 +1382,14 @@
13821382 <dict>
13831383 <key>hash2</key>
13841384 <data>
1385− TzHV+4wEf6Ct4OfAEhd4ZU3QwzVFyBaCT50Uet/ckAA=
1385+ 0RquViMJ5bGDhRzrCP7NG5r1xRpnBdss3r87Qcq7Kms=
13861386 </data>
13871387 </dict>
13881388 <key>Skrepka.pdb</key>
13891389 <dict>
13901390 <key>hash2</key>
13911391 <data>
1392− AR+okdAV5ZOA0Rj5RzoaFK5GZ4bSMLlz0J92sjaMxdY=
1392+ cUBl9b6I54xAic0oMZhTa+FIkXNVu6LHRMxd5Q4QIVE=
13931393 </data>
13941394 </dict>
13951395 <key>Sodium.Core.dll</key>
client/bin/Debug/net10.0-ios/iossimulator-arm64/Skrepka.dll

Binary file not shown.

client/bin/Debug/net10.0-ios/iossimulator-arm64/Skrepka.pdb

Binary file not shown.

client/bin/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.app/Contents/MonoBundle/Skrepka.dll

Binary file not shown.

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

Binary file not shown.

client/bin/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.app/Contents/_CodeSignature/CodeResources+2−2
@@ -240,14 +240,14 @@
240240 <dict>
241241 <key>hash2</key>
242242 <data>
243− fLiyHbdfTDvK9D25CmTS2vrZbxslf5o37h7stAFyyzc=
243+ 1YMNcB7xDPXVmXTy/5unpWgH6sFYXx+ajmUweDpLEA0=
244244 </data>
245245 </dict>
246246 <key>MonoBundle/Skrepka.pdb</key>
247247 <dict>
248248 <key>hash2</key>
249249 <data>
250− UYV1Js3NxCMbEhwDushUOfVNf2Zqfoff+tT+ijnSNAw=
250+ W+NR34Zhwrd+2gZJtPCFpFd/OzgeckZ7aIj40k0IFrw=
251251 </data>
252252 </dict>
253253 <key>MonoBundle/Sodium.Core.dll</key>
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-ios/iossimulator-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+b018bb70ad38e33f052804139428cc5ad37cf583")>]
13+[<assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0+ac8c87f242c436f4a1bad89ff035eecf82a2bb15")>]
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-ios/iossimulator-arm64/Skrepka.AssemblyInfoInputs.cache+1−1
@@ -1 +1 @@
1−57e7ff8ad3251b11511c75e432d109c5c81fd656f2685113d1e9afa4e3a11850
1+dcd7cb11d85d1b7e19d268b4a83cd5e3289a020ad2c4daa5bfa952d60173855b
client/obj/Debug/net10.0-ios/iossimulator-arm64/Skrepka.dll

Binary file not shown.

client/obj/Debug/net10.0-ios/iossimulator-arm64/Skrepka.pdb

Binary file not shown.

client/obj/Debug/net10.0-ios/iossimulator-arm64/Skrepka.sourcelink.json+1−1
@@ -1 +1 @@
1−{"documents":{"/Users/iko/Developer/skrepka/*":"https://raw.githubusercontent.com/ilyakooo0/skrepka/b018bb70ad38e33f052804139428cc5ad37cf583/*"}}
1+{"documents":{"/Users/iko/Developer/skrepka/*":"https://raw.githubusercontent.com/ilyakooo0/skrepka/ac8c87f242c436f4a1bad89ff035eecf82a2bb15/*"}}
client/obj/Debug/net10.0-ios/iossimulator-arm64/linked/Skrepka.dll

Binary file not shown.

client/obj/Debug/net10.0-ios/iossimulator-arm64/linked/Skrepka.pdb

Binary file not shown.

client/obj/Debug/net10.0-ios/iossimulator-arm64/ref/Skrepka.dll

Binary file not shown.

client/obj/Debug/net10.0-ios/iossimulator-arm64/refint/Skrepka.dll

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+b018bb70ad38e33f052804139428cc5ad37cf583")>]
13+[<assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0+ac8c87f242c436f4a1bad89ff035eecf82a2bb15")>]
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−1884cb8a1baa514aa08abae9e9488933dc9303d202768ad3435ac63803776e48
1+fb7049c87bbc40133ce9fcdb916d1edeb0d2c72163aa8e3476623d121e5e3788
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/b018bb70ad38e33f052804139428cc5ad37cf583/*"}}
1+{"documents":{"/Users/iko/Developer/skrepka/*":"https://raw.githubusercontent.com/ilyakooo0/skrepka/ac8c87f242c436f4a1bad89ff035eecf82a2bb15/*"}}
client/obj/Debug/net10.0-maccatalyst/maccatalyst-arm64/linked/Skrepka.dll

Binary file not shown.

client/obj/Debug/net10.0-maccatalyst/maccatalyst-arm64/linked/Skrepka.pdb

Binary file not shown.

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+22−22
@@ -40,9 +40,9 @@ maxForwardRetriesPerServer = 100
4040-- Types
4141-- ============================================
4242
43−type PollEvent = {id: Text, eventType: Text, payload: {encryptedBlob: Text, fromKey: Text, timestamp: Int, toKey: Text}}
43+type PollEvent = {id: Text, eventType: Text, payload: {encryptedBlob: Text, fromKey: Text, timestamp: Timestamp, toKey: Text}}
4444
45−type GossipEvent = {eventType: Text, pubkey: Text, server: Text, timestamp: Int, ttl: Int}
45+type GossipEvent = {eventType: Text, pubkey: Text, server: Text, timestamp: Timestamp, ttl: Int<S>}
4646
4747type PubkeyHex = Text where \s -> isValidHex s && not (tooLong s maxPubkeyLen)
4848type BlobHex = Text where \s -> isValidHex s && not (tooLong s maxBlobLen)
@@ -50,6 +50,8 @@ type SignatureHex = Text where \s -> isValidHex s && not (tooLong s maxSignature
5050type MessageIdHex = Text where \s -> isValidHex s && not (tooLong s maxMessageIdLen)
5151type ServerName = Text where \s -> not (tooLong s maxServerNameLen) && not (isBadServerName s)
5252
53+type Timestamp = Int<Ms>
54+
5355-- ============================================
5456-- Persisted State
5557-- ============================================
@@ -58,13 +60,13 @@ type ServerName = Text where \s -> not (tooLong s maxServerNameLen) && not (isBa
5860
5961*sessions : [{token: Text, pubkey: Text, expiresAt: Int}]
6062
61−*messages : [{messageId: Text, toKey: Text, fromKey: Text, encryptedBlob: Text, timestamp: Int, originServer: Text, receivedAt: Int}]
63+*messages : [{messageId: Text, toKey: Text, fromKey: Text, encryptedBlob: Text, timestamp: Timestamp, originServer: Text, receivedAt: Int}]
6264
63−*presence : [{pubkey: Text, server: Text, expiresAt: Int, timestamp: Int}]
65+*presence : [{pubkey: Text, server: Text, expiresAt: Int, timestamp: Timestamp}]
6466
6567*seqCounter : Int
6668
67−*forwards : [{messageId: Text, toServer: Text, createdAt: Int, toKey: Text, fromKey: Text, encryptedBlob: Text, timestamp: Int}]
69+*forwards : [{messageId: Text, toServer: Text, createdAt: Int, toKey: Text, fromKey: Text, encryptedBlob: Text, timestamp: Timestamp}]
6870
6971*ackedIds : [{messageId: Text, toKey: Text, ackedAt: Int, originServer: Text}]
7072
@@ -88,7 +90,7 @@ route ClientApi where
8890 -> {events: [PollEvent], cursor: Int, authorized: Bool}
8991 = Poll
9092 /messages
91− POST {to: PubkeyHex, encryptedBlob: BlobHex, timestamp: Int} / headers {authorization: Text}
93+ POST {to: PubkeyHex, encryptedBlob: BlobHex, timestamp: Timestamp} / headers {authorization: Text}
9294 -> {status: Text, messageId: Text}
9395 = SendMessage
9496 POST {messageIds: [MessageIdHex]} /ack headers {authorization: Text}
@@ -103,7 +105,7 @@ route FederationApi where
103105 POST {events: [GossipEvent], hopsRemaining: Int, fromServer: ServerName} /gossip headers {authorization: Text}
104106 -> {ok: Bool}
105107 = RecvGossip
106− POST {toKey: PubkeyHex, fromKey: PubkeyHex, encryptedBlob: BlobHex, timestamp: Int, originServer: ServerName, messageId: MessageIdHex} /forward headers {authorization: Text}
108+ POST {toKey: PubkeyHex, fromKey: PubkeyHex, encryptedBlob: BlobHex, timestamp: Timestamp, originServer: ServerName, messageId: MessageIdHex} /forward headers {authorization: Text}
107109 -> {ok: Bool}
108110 = ForwardMessage
109111 POST {messageIds: [Text], originServer: Text, fromServer: ServerName} /ack headers {authorization: Text}
@@ -121,6 +123,9 @@ route Api = AuthApi | ClientApi | FederationApi | MaintenanceApi
121123-- Helpers
122124-- ============================================
123125
126+msToS : Int<Ms> -> Int<S>
127+msToS = \x -> x / 1000
128+
124129hexDigit = \n ->
125130 let n = if n < 0 then 0 else if n > 15 then 15 else n in
126131 if n == 0 then "0" else if n == 1 then "1" else if n == 2 then "2" else if n == 3 then "3"
@@ -414,14 +419,12 @@ sendToEach = \servers action ->
414419 sendToEach rest action
415420
416421pruneExpired = \t -> do
417− challenges <- *challenges
418422 set *challenges = do
419− c <- challenges
423+ c <- *challenges
420424 where c.expiresAt > t
421425 yield c
422− sessions <- *sessions
423426 set *sessions = do
424− s <- sessions
427+ s <- *sessions
425428 where s.expiresAt > t
426429 yield s
427430 presenceBefore <- *presence
@@ -434,19 +437,16 @@ pruneExpired = \t -> do
434437 where p.expiresAt > t
435438 yield p
436439 set *presence = presenceAfter
437− messages <- *messages
438440 set *messages = do
439− m <- messages
441+ m <- *messages
440442 where m.receivedAt >= t - messageMaxAge
441443 yield m
442− forwards <- *forwards
443444 set *forwards = do
444− f <- forwards
445+ f <- *forwards
445446 where f.createdAt >= t - messageMaxAge
446447 yield f
447− ackedIds <- *ackedIds
448448 set *ackedIds = do
449− a <- ackedIds
449+ a <- *ackedIds
450450 where a.ackedAt >= t - messageMaxAge
451451 yield a
452452 yield {expiredLocal: (do
@@ -468,7 +468,7 @@ maybePrune = \t -> do
468468 set *lastPrunedAt = t
469469 pruneExpired t
470470
471−sendBatchedGossipToServer : Text -> [{eventType: Text, pubkey: Text, server: Text, timestamp: Int, ttl: Int}] -> IO {network} {}
471+sendBatchedGossipToServer : Text -> [{eventType: Text, pubkey: Text, server: Text, timestamp: Timestamp, ttl: Int}] -> IO {network} {}
472472sendBatchedGossipToServer = \server allEvents ->
473473 if count allEvents == 0
474474 then yield {}
@@ -485,7 +485,7 @@ sendBatchedGossipToServer = \server allEvents ->
485485 Err _ -> yield {}
486486 sendBatchedGossipToServer server rest
487487
488−sendBatchedGossip : [Text] -> [{eventType: Text, pubkey: Text, server: Text, timestamp: Int, ttl: Int}] -> IO {network} {}
488+sendBatchedGossip : [Text] -> [{eventType: Text, pubkey: Text, server: Text, timestamp: Timestamp, ttl: Int}] -> IO {network} {}
489489sendBatchedGossip = \peerServers allEvents ->
490490 if count allEvents == 0
491491 then yield {}
@@ -506,7 +506,7 @@ broadcastOfflineGossip = \t peerServers expiredLocal -> do
506506 sendBatchedGossip peerServers gossipEvents
507507 else yield {}
508508
509−retryForwardsForServer : [{encryptedBlob: Text, fromKey: Text, messageId: Text, timestamp: Int, toKey: Text, toServer: Text}] -> Int -> IO {network} {}
509+retryForwardsForServer : [{encryptedBlob: Text, fromKey: Text, messageId: Text, timestamp: Timestamp, toKey: Text, toServer: Text}] -> Int -> IO {network} {}
510510retryForwardsForServer = \entries remaining ->
511511 if count entries == 0 || remaining <= 0
512512 then yield {}
@@ -532,7 +532,7 @@ retryForwardsForServer = \entries remaining ->
532532 Err _ -> yield {}
533533 retryForwardsForServer rest (remaining - 1)
534534
535−retryPendingForwards : [{encryptedBlob: Text, fromKey: Text, messageId: Text, timestamp: Int, toKey: Text, toServer: Text}] -> IO {network} {}
535+retryPendingForwards : [{encryptedBlob: Text, fromKey: Text, messageId: Text, timestamp: Timestamp, toKey: Text, toServer: Text}] -> IO {network} {}
536536retryPendingForwards = \pending -> do
537537 let servers = fold (\acc x ->
538538 if count (do s <- acc; where s == x.toServer; yield s) > 0 then acc
@@ -636,7 +636,7 @@ pollLoop = \token cursor deadline -> do
636636 if result.authorized && count result.events == 0 && t < deadline
637637 then do
638638 let remaining = deadline - t
639− let sleepMs = if remaining > 500 then 500 else remaining
639+ let sleepMs = if remaining > 500 then 500<Ms> else remaining
640640 sleep sleepMs
641641 pollLoop token cursor deadline
642642 else yield result