Files
speckle-sharp-sdk/src/Speckle.Sdk/Credentials/GraphQLClientFactory.cs
T
Jedd Morgan ff1b688321 refactor(accountmanager): Refactor account manager for automate (#320)
* First pass refactor account manager

* Use GraphQLClient factory in account manager also

* update account references

* cleanup

* Added test + comments

* more docstrings

* More tests
2025-06-03 12:54:44 +00:00

92 lines
3.4 KiB
C#

using System.Net.WebSockets;
using System.Reflection;
using GraphQL.Client.Http;
using Microsoft.Extensions.Logging;
using Speckle.InterfaceGenerator;
using Speckle.Newtonsoft.Json;
using Speckle.Newtonsoft.Json.Serialization;
using Speckle.Sdk.Api.GraphQL.Serializer;
using Speckle.Sdk.Helpers;
namespace Speckle.Sdk.Credentials;
[GenerateAutoInterface]
public class GraphQLClientFactory(
ISpeckleApplication application,
ISpeckleHttp speckleHttp,
ILogger<GraphQLClientFactory> logger
) : IGraphQLClientFactory
{
/// <summary>
/// <inheritdoc cref="CreateGraphQLClient(Uri, string)"/>
/// </summary>
/// <param name="account">The account to use for authentication</param>
/// <returns></returns>
public GraphQLHttpClient CreateGraphQLClient(Account account)
{
return CreateGraphQLClient(new(account.serverInfo.url), account.token);
}
/// <summary>
/// Creates a <see cref="GraphQLHttpClient"/> configured for communication with a Speckle server
/// </summary>
/// <param name="serverUrl">The base url of the speckle server to communicate with</param>
/// <param name="authToken">If provided, all requests will be authenticated</param>
/// <returns></returns>
public GraphQLHttpClient CreateGraphQLClient(Uri serverUrl, string? authToken)
{
var gQLClient = new GraphQLHttpClient(
new GraphQLHttpClientOptions
{
EndPoint = new Uri(serverUrl, "/graphql"),
UseWebSocketForQueriesAndMutations = false,
WebSocketProtocol = "graphql-ws",
ConfigureWebSocketConnectionInitPayload = _ =>
{
return SpeckleHttp.CanAddAuth(authToken, out string? authValue) ? new { Authorization = authValue } : null;
},
},
new NewtonsoftJsonSerializer(
new JsonSerializerSettings()
{
ContractResolver = new CamelCasePropertyNamesContractResolver { IgnoreIsSpecifiedMembers = true }, //(Default)
MissingMemberHandling = MissingMemberHandling.Error, //(not default) If you query for a member that doesn't exist, this will throw (except websocket responses see https://github.com/graphql-dotnet/graphql-client/issues/660)
NullValueHandling = NullValueHandling.Ignore, //(not default) We won't serialize nulls, as can open more opportunity for conflicting with servers that are old and don't have the latest schema
Converters = { new ConstantCaseEnumConverter() }, //(Default) enums will be serialized using the GraphQL const case standard
}
),
CreateHttpClient(authToken)
);
gQLClient.WebSocketReceiveErrors.Subscribe(ex =>
{
if (ex is WebSocketException we)
{
logger.LogError(
we,
"GraphQL Websocket received an {WebSocketErrorCode} ({NativeErrorCode}) error that has been swallowed",
we.WebSocketErrorCode,
we.ErrorCode
);
}
else
{
logger.LogError(ex, "GraphQL Websocket received an error that has been swallowed");
}
});
return gQLClient;
}
private HttpClient CreateHttpClient(string? token)
{
var httpClient = speckleHttp.CreateHttpClient(timeoutSeconds: 30, authorizationToken: token);
httpClient.DefaultRequestHeaders.Add("apollographql-client-name", application.ApplicationAndVersion);
httpClient.DefaultRequestHeaders.Add(
"apollographql-client-version",
Assembly.GetExecutingAssembly().GetName().Version?.ToString()
);
return httpClient;
}
}