2using System.Diagnostics.CodeAnalysis;
3using System.Net.Http.Headers;
5using System.Threading.Tasks;
7using Microsoft.Extensions.Logging;
45 readonly ILogger<GraphQLServerClient>
logger;
84 ILogger<GraphQLServerClient>
logger)
88 this.logger =
logger ??
throw new ArgumentNullException(nameof(
logger));
103 ILogger<GraphQLServerClient>
logger,
106 IOperationResult<ILoginResult> loginResult)
110 ArgumentNullException.ThrowIfNull(loginResult);
114 if (!task.IsCompleted)
115 throw new InvalidOperationException($
"Expected {nameof(CreateCredentialsTuple)} to not await in constructor!");
117 bearerCredentialsTask = Task.FromResult<(AuthenticationHeaderValue Header, DateTime Exp)?>(task.Result);
127 public ValueTask<IOperationResult<TResultData>>
RunOperationAsync<TResultData>(Func<IGraphQLClient, ValueTask<IOperationResult<TResultData>>> operationExecutor, CancellationToken cancellationToken)
128 where TResultData :
class
130 ArgumentNullException.ThrowIfNull(operationExecutor);
131 return WrapAuthentication(operationExecutor, cancellationToken);
135 public ValueTask<IOperationResult<TResultData>>
RunOperation<TResultData>(Func<IGraphQLClient, Task<IOperationResult<TResultData>>> operationExecutor, CancellationToken cancellationToken)
136 where TResultData :
class
138 ArgumentNullException.ThrowIfNull(operationExecutor);
139 return WrapAuthentication(async localClient => await operationExecutor(localClient), cancellationToken);
143 public async ValueTask<IDisposable>
Subscribe<TResultData>(Func<IGraphQLClient, IObservable<IOperationResult<TResultData>>> operationExecutor, IObserver<IOperationResult<TResultData>> observer, CancellationToken cancellationToken)
144 where TResultData :
class
146 ArgumentNullException.ThrowIfNull(operationExecutor);
147 ArgumentNullException.ThrowIfNull(observer);
157 var (currentAuthHeader, expires) = tuple.Value;
158 if (expires <= DateTimeOffset.UtcNow)
159 currentAuthHeader = await
Reauthenticate(currentAuthHeader, cancellationToken).ConfigureAwait(
false);
167 return observable.Subscribe(observer);
176 => ValueTask.FromResult(
177 new AuthenticationHeaderValue(
188 async ValueTask<IOperationResult<TResultData>>
WrapAuthentication<TResultData>(Func<IGraphQLClient, ValueTask<IOperationResult<TResultData>>> operationExecutor, CancellationToken cancellationToken)
189 where TResultData :
class
192 return await operationExecutor(
graphQLClient).ConfigureAwait(
false);
198 var (currentAuthHeader, expires) = tuple.Value;
199 if (expires <= DateTimeOffset.UtcNow)
200 currentAuthHeader = await
Reauthenticate(currentAuthHeader, cancellationToken).ConfigureAwait(
false);
204 var operationResult = await operationExecutor(
graphQLClient);
206 if (operationResult.IsAuthenticationError())
208 currentAuthHeader = await
Reauthenticate(currentAuthHeader, cancellationToken).ConfigureAwait(
false);
213 return operationResult;
222 async ValueTask<AuthenticationHeaderValue>
Reauthenticate(AuthenticationHeaderValue currentToken, CancellationToken cancellationToken)
227 TaskCompletionSource<(AuthenticationHeaderValue Header, DateTime Exp)?>? tcs =
null;
231 if (!bearerCredentialsTaskLocal!.IsCompleted)
233 var currentTuple = await bearerCredentialsTaskLocal.ConfigureAwait(
false);
234 if (!currentTuple.HasValue)
237 return currentTuple.Value.Header;
244 var result = bearerCredentialsTaskLocal.Result;
245 if (result?.Header != currentToken)
247 if (!result.HasValue)
250 return result.Value.Header;
253 tcs =
new TaskCompletionSource<(AuthenticationHeaderValue, DateTime)?>();
261 var loginResult = await
graphQLClient.Login.ExecuteAsync(cancellationToken).ConfigureAwait(
false);
265 tcs.SetResult(tuple);
281 async ValueTask<(AuthenticationHeaderValue Header, DateTime Exp)>
CreateCredentialsTuple(IOperationResult<ILoginResult> loginResult)
283 var bearer = loginResult.EnsureSuccess(
logger);
287 return (Header: header, Exp: bearer.ValidTo);
Exception thrown when automatic IGraphQLServerClient authentication fails.
async ValueTask< AuthenticationHeaderValue > Reauthenticate(AuthenticationHeaderValue currentToken, CancellationToken cancellationToken)
Attempt to reauthenticate.
readonly IAsyncDisposable serviceProvider
The IAsyncDisposable to be DisposeAsync'd with the GraphQLServerClient.
GraphQLServerClient(IGraphQLClient graphQLClient, IAsyncDisposable serviceProvider, ILogger< GraphQLServerClient > logger, Action< AuthenticationHeaderValue > setAuthenticationHeader, AuthenticationHeaderValue? basicCredentialsHeader, IOperationResult< ILoginResult > loginResult)
Initializes a new instance of the GraphQLServerClient class.
Task<(AuthenticationHeaderValue Header, DateTime Exp)?>? bearerCredentialsTask
A Task<TResult> resulting in a ValueTuple<T1, T2> containing the current AuthenticationHeaderValue fo...
GraphQLServerClient(IGraphQLClient graphQLClient, IAsyncDisposable serviceProvider, ILogger< GraphQLServerClient > logger)
Initializes a new instance of the GraphQLServerClient class.
async ValueTask< IOperationResult< TResultData > > WrapAuthentication< TResultData >(Func< IGraphQLClient, ValueTask< IOperationResult< TResultData > > > operationExecutor, CancellationToken cancellationToken)
Executes a given operationExecutor , potentially accounting for authentication issues.
async ValueTask<(AuthenticationHeaderValue Header, DateTime Exp)> CreateCredentialsTuple(IOperationResult< ILoginResult > loginResult)
Attempt to create the ValueTuple<T1, T2> for bearerCredentialsTask.
readonly ILogger< GraphQLServerClient > logger
The ILogger for the GraphQLServerClient.
static void ThrowOtherCallerFailedAuthException()
Throws an AuthenticationException for a login error that previously occured outside of the current ca...
virtual ValueTask< AuthenticationHeaderValue > CreateUpdatedAuthenticationHeader(string bearer)
Create a AuthenticationHeaderValue from a given bearer token.
readonly IGraphQLClient graphQLClient
The IGraphQLClient for the GraphQLServerClient.
ValueTask< IOperationResult< TResultData > > RunOperation< TResultData >(Func< IGraphQLClient, Task< IOperationResult< TResultData > > > operationExecutor, CancellationToken cancellationToken)
Runs a given operationExecutor . It may be invoked multiple times depending on the behavior of the IG...
virtual ValueTask DisposeAsync()
ValueTask< IOperationResult< TResultData > > RunOperationAsync< TResultData >(Func< IGraphQLClient, ValueTask< IOperationResult< TResultData > > > operationExecutor, CancellationToken cancellationToken)
Runs a given operationExecutor . It may be invoked multiple times depending on the behavior of the IG...
bool Authenticated
If the GraphQLServerClient was initially authenticated.
readonly? Action< AuthenticationHeaderValue > setAuthenticationHeader
The Action<T> which sets the AuthenticationHeaderValue for HTTP request in the current async context.
async ValueTask< IDisposable > Subscribe< TResultData >(Func< IGraphQLClient, IObservable< IOperationResult< TResultData > > > operationExecutor, IObserver< IOperationResult< TResultData > > observer, CancellationToken cancellationToken)
Subcribes to the GraphQL subscription indicated by operationExecutor .A ValueTask<TResult> resulting ...
readonly? object bearerCredentialsHeaderTaskLock
lock object used to synchronize access to bearerCredentialsTask.
bool CanReauthenticate
If the GraphQLServerClient supports reauthentication.
readonly? AuthenticationHeaderValue basicCredentialsHeader
The AuthenticationHeaderValue containing the authenticated user's password credentials.
Wrapper for using a TGS IGraphQLClient.