11 let c = new HttpClient()
12 c.Timeout <- TimeSpan.FromSeconds(60.)
15 let private postJson (url: string) (body: string) (token: string option) =
17 let request = new HttpRequestMessage(HttpMethod.Post, url)
18 request.Content <- new StringContent(body, Encoding.UTF8, "application/json")
21 |> Option.iter (fun t -> request.Headers.Add("Authorization", $"Bearer {t}"))
23 let! response = client.SendAsync(request) |> Async.AwaitTask
24 let! text = response.Content.ReadAsStringAsync() |> Async.AwaitTask
25 return JsonDocument.Parse(text)
28 let private getJson (url: string) (token: string) =
30 let request = new HttpRequestMessage(HttpMethod.Get, url)
31 request.Headers.Add("Authorization", $"Bearer {token}")
32 let! response = client.SendAsync(request) |> Async.AwaitTask
33 let! text = response.Content.ReadAsStringAsync() |> Async.AwaitTask
34 return JsonDocument.Parse(text)
37 let private expectObject (doc: JsonDocument) (endpoint: string) =
38 let root = doc.RootElement
40 if root.ValueKind <> JsonValueKind.Object then
41 failwith $"{endpoint}: unexpected response: {root.GetRawText()}"
43 match root.TryGetProperty("error") with
44 | true, err -> failwith $"{endpoint}: server error: {err.GetString()}"
47 let requestChallenge (serverUrl: string) (pubkeyHex: string) =
49 let body = $"""{{ "pubkey": "{pubkeyHex}" }}"""
50 let! doc = postJson $"{serverUrl}/auth/challenge" body None
51 let root = expectObject doc "/auth/challenge"
52 return root.GetProperty("challenge").GetString()
55 let verify (serverUrl: string) (pubkeyHex: string) (challenge: string) (signatureHex: string) =
58 $"""{{ "pubkey": "{pubkeyHex}", "challenge": "{challenge}", "signature": "{signatureHex}" }}"""
60 let! doc = postJson $"{serverUrl}/auth/verify" body None
61 let root = expectObject doc "/auth/verify"
62 let token = root.GetProperty("token").GetString()
63 let expiresAt = root.GetProperty("expiresAt").GetInt64()
64 return (token, expiresAt)
67 let sendMessage (serverUrl: string) (token: string) (toHex: string) (blobHex: string) (timestamp: int64) =
70 $"""{{ "to": "{toHex}", "encryptedBlob": "{blobHex}", "timestamp": {timestamp} }}"""
72 let! doc = postJson $"{serverUrl}/messages" body (Some token)
73 let root = expectObject doc "/messages"
74 let status = root.GetProperty("status").GetString()
75 let msgId = root.GetProperty("messageId").GetString()
76 return (status, msgId)
79 let private pollClient =
80 let c = new HttpClient()
81 c.Timeout <- System.Threading.Timeout.InfiniteTimeSpan
84 let poll (serverUrl: string) (token: string) =
86 let request = new HttpRequestMessage(HttpMethod.Get, $"{serverUrl}/poll?timeout=30")
87 request.Headers.Add("Authorization", $"Bearer {token}")
89 let! response = pollClient.SendAsync(request) |> Async.AwaitTask
90 let! text = response.Content.ReadAsStringAsync() |> Async.AwaitTask
91 let doc = JsonDocument.Parse(text)
92 let root = expectObject doc "/poll"
93 let cursor = root.GetProperty("cursor").GetString()
95 // Knot runtime wraps relations as [[record1, record2, ...]]
96 let eventsOuter = root.GetProperty("events")
99 if eventsOuter.GetArrayLength() > 0
100 && eventsOuter.[0].ValueKind = JsonValueKind.Array then
106 [ for e in eventsInner.EnumerateArray() do
107 if e.ValueKind = JsonValueKind.Object then
108 let id = e.GetProperty("id").GetString()
109 let evtType = e.GetProperty("eventType").GetString()
110 let payload = e.GetProperty("payload").GetString()
111 yield (id, evtType, payload) ]
113 return (events, cursor)
116 let ackMessages (serverUrl: string) (token: string) (messageIds: string list) =
119 messageIds |> List.map (fun id -> $"\"{id}\"") |> String.concat ", "
121 let body = $"""{{ "messageIds": [{idsJson}] }}"""
122 let! _ = postJson $"{serverUrl}/messages/ack" body (Some token)
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list