tgstation-server 6.9.2
The /tg/station 13 server suite
Loading...
Searching...
No Matches
ApiClient.cs
Go to the documentation of this file.
1using System;
2using System.Collections.Generic;
3using System.IO;
4using System.Linq;
5using System.Net;
6using System.Net.Http;
7using System.Net.Http.Headers;
8using System.Net.Mime;
9using System.Text;
10using System.Threading;
11using System.Threading.Tasks;
12using System.Web;
13
14using Microsoft.AspNetCore.Http.Connections;
15using Microsoft.AspNetCore.SignalR.Client;
16using Microsoft.Extensions.DependencyInjection;
17using Microsoft.Extensions.Logging;
18using Microsoft.Net.Http.Headers;
19
20using Newtonsoft.Json;
21using Newtonsoft.Json.Converters;
22using Newtonsoft.Json.Serialization;
23
30
32{
35 {
40 static readonly HttpMethod HttpPatch = new("PATCH");
41
43 public Uri Url { get; }
44
47 {
48 get => headers;
49 set => headers = value ?? throw new InvalidOperationException("Cannot set null headers!");
50 }
51
53 public TimeSpan Timeout
54 {
55 get => httpClient.Timeout;
56 set => httpClient.Timeout = value;
57 }
58
62 static readonly JsonSerializerSettings SerializerSettings = new()
63 {
64 ContractResolver = new CamelCasePropertyNamesContractResolver(),
65 Converters = new[]
66 {
67 new VersionConverter(),
68 },
69 };
70
75
79 readonly List<IRequestLogger> requestLoggers;
80
84 readonly List<HubConnection> hubConnections;
85
90
94 readonly SemaphoreSlim semaphoreSlim;
95
99 readonly bool authless;
100
105
110
116 static void HandleBadResponse(HttpResponseMessage response, string json)
117 {
118 ErrorMessageResponse? errorMessage = null;
119 try
120 {
121 // check if json serializes to an error message
122 errorMessage = JsonConvert.DeserializeObject<ErrorMessageResponse>(json, SerializerSettings);
123 }
124 catch (JsonException)
125 {
126 }
127
128#pragma warning disable IDE0010 // Add missing cases
129 switch (response.StatusCode)
130#pragma warning restore IDE0010 // Add missing cases
131 {
132 case HttpStatusCode.Unauthorized:
133 throw new UnauthorizedException(errorMessage, response);
134 case HttpStatusCode.InternalServerError:
135 throw new ServerErrorException(errorMessage, response);
136 case HttpStatusCode.NotImplemented:
137 // unprocessable entity
138 case (HttpStatusCode)422:
139 throw new MethodNotSupportedException(errorMessage, response);
140 case HttpStatusCode.NotFound:
141 case HttpStatusCode.Gone:
142 case HttpStatusCode.Conflict:
143 throw new ConflictException(errorMessage, response);
144 case HttpStatusCode.Forbidden:
145 throw new InsufficientPermissionsException(response);
146 case HttpStatusCode.ServiceUnavailable:
147 throw new ServiceUnavailableException(response);
148 case HttpStatusCode.RequestTimeout:
149 throw new RequestTimeoutException(response);
150 case (HttpStatusCode)429:
151 throw new RateLimitException(errorMessage, response);
152 default:
153 if (errorMessage?.ErrorCode == ErrorCode.ApiMismatch)
154 throw new VersionMismatchException(errorMessage, response);
155
156 throw new ApiConflictException(errorMessage, response);
157 }
158 }
159
168 public ApiClient(
170 Uri url,
171 ApiHeaders apiHeaders,
173 bool authless)
174 {
175 this.httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
176 Url = url ?? throw new ArgumentNullException(nameof(url));
177 headers = apiHeaders ?? throw new ArgumentNullException(nameof(apiHeaders));
178 this.tokenRefreshHeaders = tokenRefreshHeaders;
179 this.authless = authless;
180
181 requestLoggers = new List<IRequestLogger>();
182 hubConnections = new List<HubConnection>();
183 semaphoreSlim = new SemaphoreSlim(1);
184 }
185
187 public async ValueTask DisposeAsync()
188 {
189 List<HubConnection> localHubConnections;
190 lock (hubConnections)
191 {
192 if (disposed)
193 return;
194
195 disposed = true;
196
197 localHubConnections = hubConnections.ToList();
198 hubConnections.Clear();
199 }
200
201 await ValueTaskExtensions.WhenAll(hubConnections.Select(connection => connection.DisposeAsync()));
202
203 httpClient.Dispose();
204 semaphoreSlim.Dispose();
205 }
206
208 public ValueTask<TResult> Create<TResult>(string route, CancellationToken cancellationToken)
209 => RunRequest<object, TResult>(route, new object(), HttpMethod.Put, null, false, cancellationToken);
210
212 public ValueTask<TResult> Read<TResult>(string route, CancellationToken cancellationToken)
213 => RunRequest<object, TResult>(route, null, HttpMethod.Get, null, false, cancellationToken);
214
216 public ValueTask<TResult> Update<TResult>(string route, CancellationToken cancellationToken)
217 => RunRequest<object, TResult>(route, new object(), HttpMethod.Post, null, false, cancellationToken);
218
220 public ValueTask<TResult> Update<TBody, TResult>(string route, TBody body, CancellationToken cancellationToken)
221 where TBody : class
222 => RunRequest<TBody, TResult>(route, body, HttpMethod.Post, null, false, cancellationToken);
223
225 public ValueTask Patch(string route, CancellationToken cancellationToken) => RunRequest(route, HttpPatch, null, false, cancellationToken);
226
228 public ValueTask Update<TBody>(string route, TBody body, CancellationToken cancellationToken)
229 where TBody : class
230 => RunResultlessRequest(route, body, HttpMethod.Post, null, false, cancellationToken);
231
233 public ValueTask<TResult> Create<TBody, TResult>(string route, TBody body, CancellationToken cancellationToken)
234 where TBody : class
235 => RunRequest<TBody, TResult>(route, body, HttpMethod.Put, null, false, cancellationToken);
236
238 public ValueTask Delete(string route, CancellationToken cancellationToken)
239 => RunRequest(route, HttpMethod.Delete, null, false, cancellationToken);
240
242 public ValueTask<TResult> Create<TBody, TResult>(string route, TBody body, long instanceId, CancellationToken cancellationToken)
243 where TBody : class
244 => RunRequest<TBody, TResult>(route, body, HttpMethod.Put, instanceId, false, cancellationToken);
245
247 public ValueTask<TResult> Read<TResult>(string route, long instanceId, CancellationToken cancellationToken)
248 => RunRequest<TResult>(route, null, HttpMethod.Get, instanceId, false, cancellationToken);
249
251 public ValueTask<TResult> Update<TBody, TResult>(string route, TBody body, long instanceId, CancellationToken cancellationToken)
252 where TBody : class
253 => RunRequest<TBody, TResult>(route, body, HttpMethod.Post, instanceId, false, cancellationToken);
254
256 public ValueTask Delete(string route, long instanceId, CancellationToken cancellationToken)
257 => RunRequest(route, HttpMethod.Delete, instanceId, false, cancellationToken);
258
260 public ValueTask Delete<TBody>(string route, TBody body, long instanceId, CancellationToken cancellationToken)
261 where TBody : class
262 => RunResultlessRequest(route, body, HttpMethod.Delete, instanceId, false, cancellationToken);
263
265 public ValueTask<TResult> Delete<TResult>(string route, long instanceId, CancellationToken cancellationToken)
266 => RunRequest<TResult>(route, null, HttpMethod.Delete, instanceId, false, cancellationToken);
267
269 public ValueTask<TResult> Delete<TBody, TResult>(string route, TBody body, long instanceId, CancellationToken cancellationToken)
270 where TBody : class
271 => RunRequest<TBody, TResult>(route, body, HttpMethod.Delete, instanceId, false, cancellationToken);
272
274 public ValueTask<TResult> Create<TResult>(string route, long instanceId, CancellationToken cancellationToken)
275 => RunRequest<object, TResult>(route, new object(), HttpMethod.Put, instanceId, false, cancellationToken);
276
278 public ValueTask<TResult> Patch<TResult>(string route, long instanceId, CancellationToken cancellationToken)
279 => RunRequest<object, TResult>(route, new object(), HttpPatch, instanceId, false, cancellationToken);
280
282 public void AddRequestLogger(IRequestLogger requestLogger) => requestLoggers.Add(requestLogger ?? throw new ArgumentNullException(nameof(requestLogger)));
283
285 public ValueTask<Stream> Download(FileTicketResponse ticket, CancellationToken cancellationToken)
286 {
287 if (ticket == null)
288 throw new ArgumentNullException(nameof(ticket));
289
290 return RunRequest<Stream>(
291 $"{Routes.Transfer}?ticket={HttpUtility.UrlEncode(ticket.FileTicket)}",
292 null,
293 HttpMethod.Get,
294 null,
295 false,
296 cancellationToken);
297 }
298
300 public async ValueTask Upload(FileTicketResponse ticket, Stream? uploadStream, CancellationToken cancellationToken)
301 {
302 if (ticket == null)
303 throw new ArgumentNullException(nameof(ticket));
304
305 MemoryStream? memoryStream = null;
306 if (uploadStream == null)
307 memoryStream = new MemoryStream();
308
309 using (memoryStream)
310 {
311 var streamContent = new StreamContent(uploadStream ?? memoryStream);
312 try
313 {
314 await RunRequest<object>(
315 $"{Routes.Transfer}?ticket={HttpUtility.UrlEncode(ticket.FileTicket)}",
316 streamContent,
317 HttpMethod.Put,
318 null,
319 false,
320 cancellationToken)
321 .ConfigureAwait(false);
322 streamContent = null;
323 }
324 finally
325 {
326 streamContent?.Dispose();
327 }
328 }
329 }
330
336 public async ValueTask<bool> RefreshToken(CancellationToken cancellationToken)
337 {
338 if (tokenRefreshHeaders == null)
339 return false;
340
341 var startingToken = headers.Token;
342 await semaphoreSlim.WaitAsync(cancellationToken).ConfigureAwait(false);
343 try
344 {
345 if (startingToken != headers.Token)
346 return true;
347
348 var token = await RunRequest<object, TokenResponse>(Routes.ApiRoot, new object(), HttpMethod.Post, null, true, cancellationToken).ConfigureAwait(false);
349 headers = new ApiHeaders(headers.UserAgent!, token);
350 }
351 finally
352 {
353 semaphoreSlim.Release();
354 }
355
356 return true;
357 }
358
360 public async ValueTask<IAsyncDisposable> CreateHubConnection<THubImplementation>(
361 THubImplementation hubImplementation,
362 IRetryPolicy? retryPolicy,
363 Action<ILoggingBuilder>? loggingConfigureAction,
364 CancellationToken cancellationToken)
365 where THubImplementation : class
366 {
367 if (hubImplementation == null)
368 throw new ArgumentNullException(nameof(hubImplementation));
369
370 retryPolicy ??= new InfiniteThirtySecondMaxRetryPolicy();
371
372 var wrappedPolicy = new ApiClientTokenRefreshRetryPolicy(this, retryPolicy);
373
374 HubConnection? hubConnection = null;
375 var hubConnectionBuilder = new HubConnectionBuilder()
376 .AddNewtonsoftJsonProtocol(options =>
377 {
378 options.PayloadSerializerSettings = SerializerSettings;
379 })
380 .WithAutomaticReconnect(wrappedPolicy)
381 .WithUrl(
382 new Uri(Url, Routes.JobsHub),
383 HttpTransportType.ServerSentEvents,
384 options =>
385 {
386 options.AccessTokenProvider = async () =>
387 {
388 // DCT: None available.
389 if (Headers.Token == null
390 || (Headers.Token.ParseJwt().ValidTo <= DateTime.UtcNow
391 && !await RefreshToken(CancellationToken.None)))
392 {
393 _ = hubConnection!.StopAsync(); // DCT: None available.
394 return null;
395 }
396
397 return Headers.Token.Bearer;
398 };
399
400 options.CloseTimeout = Timeout;
401
402 Headers.SetHubConnectionHeaders(options.Headers);
403 });
404
405 if (loggingConfigureAction != null)
406 hubConnectionBuilder.ConfigureLogging(loggingConfigureAction);
407
408 hubConnection = hubConnectionBuilder.Build();
409 try
410 {
411 hubConnection.Closed += async (error) =>
412 {
413 if (error is HttpRequestException httpRequestException)
414 {
415 // .StatusCode isn't in netstandard but fuck the police
416 var property = error.GetType().GetProperty("StatusCode");
417 if (property != null)
418 {
419 var statusCode = (HttpStatusCode?)property.GetValue(error);
420 if (statusCode == HttpStatusCode.Unauthorized
421 && !await RefreshToken(CancellationToken.None))
422 _ = hubConnection!.StopAsync();
423 }
424 }
425 };
426
427 hubConnection.ProxyOn(hubImplementation);
428
429 Task startTask;
430 lock (hubConnections)
431 {
432 if (disposed)
433 throw new ObjectDisposedException(nameof(ApiClient));
434
435 hubConnections.Add(hubConnection);
436 startTask = hubConnection.StartAsync(cancellationToken);
437 }
438
439 await startTask;
440
441 return hubConnection;
442 }
443 catch
444 {
445 bool needsDispose;
446 lock (hubConnections)
447 needsDispose = hubConnections.Remove(hubConnection);
448
449 if (needsDispose)
450 await hubConnection.DisposeAsync();
451 throw;
452 }
453 }
454
466#pragma warning disable CA1506 // TODO: Decomplexify
467 protected virtual async ValueTask<TResult> RunRequest<TResult>(
468 string route,
469 HttpContent? content,
470 HttpMethod method,
471 long? instanceId,
472 bool tokenRefresh,
473 CancellationToken cancellationToken)
474 {
475 if (route == null)
476 throw new ArgumentNullException(nameof(route));
477 if (method == null)
478 throw new ArgumentNullException(nameof(method));
479 if (content == null && (method == HttpMethod.Post || method == HttpMethod.Put))
480 throw new InvalidOperationException("content cannot be null for POST or PUT!");
481
482 if (disposed)
483 throw new ObjectDisposedException(nameof(ApiClient));
484
485 HttpResponseMessage response;
486 var fullUri = new Uri(Url, route);
487 var serializerSettings = SerializerSettings;
488 var fileDownload = typeof(TResult) == typeof(Stream);
489 using (var request = new HttpRequestMessage(method, fullUri))
490 {
491 if (content != null)
492 request.Content = content;
493
494 try
495 {
496 var headersToUse = tokenRefresh ? tokenRefreshHeaders! : headers;
497 headersToUse.SetRequestHeaders(request.Headers, instanceId);
498
499 if (authless)
500 request.Headers.Remove(HeaderNames.Authorization);
501 else
502 {
503 var bearer = headersToUse.Token?.Bearer;
504 if (bearer != null)
505 {
506 var parsed = headersToUse.Token!.ParseJwt();
507 var nbf = parsed.ValidFrom;
508 var now = DateTime.UtcNow;
509 if (nbf >= now)
510 {
511 var delay = (nbf - now).Add(TimeSpan.FromMilliseconds(1));
512 await Task.Delay(delay, cancellationToken);
513 }
514 }
515 }
516
517 if (fileDownload)
518 request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Octet));
519
520 await ValueTaskExtensions.WhenAll(requestLoggers.Select(x => x.LogRequest(request, cancellationToken))).ConfigureAwait(false);
521
522 response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
523 }
524 finally
525 {
526 // prevent content param from getting disposed
527 request.Content = null;
528 }
529 }
530
531 try
532 {
533 await ValueTaskExtensions.WhenAll(requestLoggers.Select(x => x.LogResponse(response, cancellationToken))).ConfigureAwait(false);
534
535 // just stream
536 if (fileDownload && response.IsSuccessStatusCode)
537 return (TResult)(object)await CachedResponseStream.Create(response).ConfigureAwait(false);
538 }
539 catch
540 {
541 response.Dispose();
542 throw;
543 }
544
545 using (response)
546 {
547 var json = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
548
549 if (!response.IsSuccessStatusCode)
550 {
551 if (!tokenRefresh
552 && response.StatusCode == HttpStatusCode.Unauthorized
553 && await RefreshToken(cancellationToken).ConfigureAwait(false))
554 return await RunRequest<TResult>(route, content, method, instanceId, false, cancellationToken).ConfigureAwait(false);
555 HandleBadResponse(response, json);
556 }
557
558 if (String.IsNullOrWhiteSpace(json))
559 json = JsonConvert.SerializeObject(new object());
560
561 try
562 {
563 var result = JsonConvert.DeserializeObject<TResult>(json, serializerSettings);
564 return result!;
565 }
566 catch (JsonException)
567 {
568 throw new UnrecognizedResponseException(response);
569 }
570 }
571 }
572#pragma warning restore CA1506
573
586 async ValueTask<TResult> RunRequest<TBody, TResult>(
587 string route,
588 TBody? body,
589 HttpMethod method,
590 long? instanceId,
591 bool tokenRefresh,
592 CancellationToken cancellationToken)
593 where TBody : class
594 {
595 HttpContent? content = null;
596 if (body != null)
597 content = new StringContent(
598 JsonConvert.SerializeObject(body, typeof(TBody), Formatting.None, SerializerSettings),
599 Encoding.UTF8,
601
602 using (content)
603 return await RunRequest<TResult>(
604 route,
605 content,
606 method,
607 instanceId,
608 tokenRefresh,
609 cancellationToken)
610 .ConfigureAwait(false);
611 }
612
624 async ValueTask RunResultlessRequest<TBody>(
625 string route,
626 TBody? body,
627 HttpMethod method,
628 long? instanceId,
629 bool tokenRefresh,
630 CancellationToken cancellationToken)
631 where TBody : class
632 => await RunRequest<TBody, object>(
633 route,
634 body,
635 method,
636 instanceId,
637 tokenRefresh,
638 cancellationToken);
639
649 ValueTask RunRequest(
650 string route,
651 HttpMethod method,
652 long? instanceId,
653 bool tokenRefresh,
654 CancellationToken cancellationToken)
655 => RunResultlessRequest<object>(
656 route,
657 null,
658 method,
659 instanceId,
660 tokenRefresh,
661 cancellationToken);
662 }
663}
Represents the header that must be present for every server request.
Definition ApiHeaders.cs:25
ProductHeaderValue? UserAgent
The client's user agent as a ProductHeaderValue if valid.
Definition ApiHeaders.cs:89
void SetHubConnectionHeaders(IDictionary< string, string > headers)
Adds the headers necessary for a SignalR hub connection.
TokenResponse? Token
The client's TokenResponse.
void SetRequestHeaders(HttpRequestHeaders headers, long? instanceId=null)
Set HttpRequestHeaders using the ApiHeaders. This initially clears headers .
const string ApplicationJsonMime
Added to MediaTypeNames.Application in netstandard2.1. Can't use because of lack of ....
Definition ApiHeaders.cs:59
Represents an error message returned by the server.
Response for when file transfers are necessary.
Routes to a server actions.
Definition Routes.cs:9
const string ApiRoot
The root of API methods.
Definition Routes.cs:13
const string JobsHub
The root route of all hubs.
Definition Routes.cs:113
readonly? ApiHeaders tokenRefreshHeaders
Backing field for Headers.
Definition ApiClient.cs:89
TimeSpan Timeout
The request timeout.
Definition ApiClient.cs:54
static readonly JsonSerializerSettings SerializerSettings
The JsonSerializerSettings to use.
Definition ApiClient.cs:62
ValueTask< Stream > Download(FileTicketResponse ticket, CancellationToken cancellationToken)
Downloads a file Stream for a given ticket .A ValueTask<TResult> resulting in the downloaded Stream.
Definition ApiClient.cs:285
void AddRequestLogger(IRequestLogger requestLogger)
Adds a requestLogger to the request pipeline.
async ValueTask< TResult > RunRequest< TBody, TResult >(string route, TBody? body, HttpMethod method, long? instanceId, bool tokenRefresh, CancellationToken cancellationToken)
Main request method.
Definition ApiClient.cs:586
readonly IHttpClient httpClient
The IHttpClient for the ApiClient.
Definition ApiClient.cs:74
async ValueTask Upload(FileTicketResponse ticket, Stream? uploadStream, CancellationToken cancellationToken)
Uploads a given uploadStream for a given ticket .A ValueTask representing the running operation.
Definition ApiClient.cs:300
ValueTask< TResult > Delete< TResult >(string route, long instanceId, CancellationToken cancellationToken)
Run an HTTP DELETE request.A ValueTask<TResult> resulting in the response body as a TResult .
Uri Url
The Uri pointing the tgstation-server.
Definition ApiClient.cs:43
ValueTask Delete(string route, CancellationToken cancellationToken)
Run an HTTP DELETE request.A ValueTask representing the running operation.
ValueTask< TResult > Create< TBody, TResult >(string route, TBody body, CancellationToken cancellationToken)
Run an HTTP PUT request.A ValueTask<TResult> resulting in the response body as a TResult .
ApiHeaders headers
Backing field for Headers.
Definition ApiClient.cs:104
ValueTask< TResult > Update< TBody, TResult >(string route, TBody body, CancellationToken cancellationToken)
Run an HTTP POST request.A ValueTask<TResult> resulting in the response body as a TResult .
ValueTask Delete< TBody >(string route, TBody body, long instanceId, CancellationToken cancellationToken)
Run an HTTP DELETE request.A ValueTask representing the running operation.
ValueTask< TResult > Read< TResult >(string route, CancellationToken cancellationToken)
Run an HTTP GET request.A ValueTask<TResult> resulting in the response body as a TResult .
ValueTask Patch(string route, CancellationToken cancellationToken)
Run an HTTP PATCH request.A ValueTask representing the running operation.
static void HandleBadResponse(HttpResponseMessage response, string json)
Handle a bad HTTP response .
Definition ApiClient.cs:116
static readonly HttpMethod HttpPatch
PATCH HttpMethod.
Definition ApiClient.cs:40
ValueTask< TResult > Delete< TBody, TResult >(string route, TBody body, long instanceId, CancellationToken cancellationToken)
Run an HTTP DELETE request.A ValueTask representing the running operation.
virtual async ValueTask< TResult > RunRequest< TResult >(string route, HttpContent? content, HttpMethod method, long? instanceId, bool tokenRefresh, CancellationToken cancellationToken)
Main request method.
Definition ApiClient.cs:467
readonly List< IRequestLogger > requestLoggers
The IRequestLoggers used by the ApiClient.
Definition ApiClient.cs:79
async ValueTask< IAsyncDisposable > CreateHubConnection< THubImplementation >(THubImplementation hubImplementation, IRetryPolicy? retryPolicy, Action< ILoggingBuilder >? loggingConfigureAction, CancellationToken cancellationToken)
Subscribe to all job updates available to the IServerClient.An IAsyncDisposable representing the life...
Definition ApiClient.cs:360
ValueTask< TResult > Create< TResult >(string route, CancellationToken cancellationToken)
Run an HTTP PUT request.A ValueTask<TResult> resulting in the response body as a TResult .
ValueTask< TResult > Update< TResult >(string route, CancellationToken cancellationToken)
Run an HTTP POST request.A ValueTask<TResult> resulting in the response body as a TResult .
ValueTask< TResult > Patch< TResult >(string route, long instanceId, CancellationToken cancellationToken)
Run an HTTP PATCH request.A ValueTask<TResult> resulting in the response body as a TResult .
bool disposed
If the ApiClient is disposed.
Definition ApiClient.cs:109
readonly bool authless
If the authentication header should be stripped from requests.
Definition ApiClient.cs:99
readonly SemaphoreSlim semaphoreSlim
The SemaphoreSlim for TokenResponse refreshes.
Definition ApiClient.cs:94
ValueTask RunRequest(string route, HttpMethod method, long? instanceId, bool tokenRefresh, CancellationToken cancellationToken)
Main request method.
ValueTask Delete(string route, long instanceId, CancellationToken cancellationToken)
Run an HTTP DELETE request.A ValueTask representing the running operation.
readonly List< HubConnection > hubConnections
List of HubConnections created by the ApiClient.
Definition ApiClient.cs:84
async ValueTask< bool > RefreshToken(CancellationToken cancellationToken)
Attempt to refresh the stored Bearer token in Headers.
Definition ApiClient.cs:336
ApiHeaders Headers
The ApiHeaders the IApiClient uses.
Definition ApiClient.cs:47
ValueTask Update< TBody >(string route, TBody body, CancellationToken cancellationToken)
Run an HTTP POST request.A ValueTask representing the running operation.
ApiClient(IHttpClient httpClient, Uri url, ApiHeaders apiHeaders, ApiHeaders? tokenRefreshHeaders, bool authless)
Initializes a new instance of the ApiClient class.
Definition ApiClient.cs:168
A IRetryPolicy that attempts to refresh a given apiClient's token on the first disconnect.
Occurs when the server returns a bad request response if the ApiException.ErrorCode is present....
Occurs when the client performs an action that would result in data conflict.
A IRetryPolicy that returns seconds in powers of 2, maxing out at 30s.
Occurs when the client attempts to perform an action they do not have the rights for.
Occurs when the client tries to use a currently unsupported API.
Occurs when a GitHub rate limit occurs.
Occurs when the client provides invalid credentials.
Occurs when an error occurs in the server.
Occurs when the client makes a request while the server is starting or stopping.
Occurs when the client provides invalid credentials.
Occurs when a response is received that did not deserialize to one of the expected Api....
Occurs when the API version of the client is not compatible with the server's.
Extension methods for the ValueTask and ValueTask<TResult> classes.
static async ValueTask WhenAll(IEnumerable< ValueTask > tasks)
Fully await a given list of tasks .
Caches the Stream from a HttpResponseMessage for later use.
static async ValueTask< CachedResponseStream > Create(HttpResponseMessage response)
Asyncronously creates a new CachedResponseStream.
Web interface for the API.
Definition IApiClient.cs:19
For logging HTTP requests and responses.
TimeSpan Timeout
The request timeout.
ErrorCode
Types of Response.ErrorMessageResponses that the API may return.
Definition ErrorCode.cs:12