Introduced ReceiverApiClient.cs, a typed HTTP client for the EnvelopeGenerator receiver API. It provides strongly-typed methods for authentication, envelope and document retrieval, annotation/signature actions, read-only sharing, logout, and localization. The client uses dependency injection, handles error logging, and ensures authentication cookies are attached via same-origin requests.
215 lines
8.2 KiB
C#
215 lines
8.2 KiB
C#
using System.Net;
|
|
using System.Net.Http.Json;
|
|
using EnvelopeGenerator.ReceiverUI.Web.Client.Api.Models;
|
|
|
|
namespace EnvelopeGenerator.ReceiverUI.Web.Client.Api;
|
|
|
|
/// <summary>
|
|
/// Typed HTTP client for the EnvelopeGenerator receiver API.
|
|
/// All endpoints are routed through the BFF (same origin), so the
|
|
/// authentication cookie set by the API is automatically attached
|
|
/// by the browser to every request issued by the injected HttpClient.
|
|
/// </summary>
|
|
public class ReceiverApiClient
|
|
{
|
|
private readonly HttpClient _http;
|
|
private readonly ILogger<ReceiverApiClient> _logger;
|
|
|
|
public ReceiverApiClient(HttpClient http, ILogger<ReceiverApiClient> logger)
|
|
{
|
|
_http = http;
|
|
_logger = logger;
|
|
}
|
|
|
|
// ?? Receiver Auth ????????????????????????????????????????????????
|
|
|
|
public Task<ReceiverAuthResponse?> GetStatusAsync(string envelopeKey, CancellationToken ct = default)
|
|
=> GetAuthAsync($"api/receiverauth/{Uri.EscapeDataString(envelopeKey)}/status", ct);
|
|
|
|
public Task<ReceiverAuthResponse?> SubmitAccessCodeAsync(string envelopeKey, AccessCodeRequest req, CancellationToken ct = default)
|
|
=> PostAuthAsync($"api/receiverauth/{Uri.EscapeDataString(envelopeKey)}/access-code", req, ct);
|
|
|
|
public Task<ReceiverAuthResponse?> SubmitTfaCodeAsync(string envelopeKey, TfaCodeRequest req, CancellationToken ct = default)
|
|
=> PostAuthAsync($"api/receiverauth/{Uri.EscapeDataString(envelopeKey)}/tfa", req, ct);
|
|
|
|
// ?? Envelope Receiver ????????????????????????????????????????????
|
|
|
|
public async Task<EnvelopeReceiverDto?> GetEnvelopeReceiverAsync(string envelopeKey, CancellationToken ct = default)
|
|
{
|
|
var res = await _http.GetAsync($"api/envelopereceiver/{Uri.EscapeDataString(envelopeKey)}", ct);
|
|
if (!res.IsSuccessStatusCode)
|
|
return null;
|
|
return await res.Content.ReadFromJsonAsync<EnvelopeReceiverDto>(cancellationToken: ct);
|
|
}
|
|
|
|
/// <summary>Downloads the document bytes for the receiver to display in a PDF viewer.</summary>
|
|
public async Task<byte[]?> GetDocumentAsync(string envelopeKey, CancellationToken ct = default)
|
|
{
|
|
var res = await _http.GetAsync($"api/document/{Uri.EscapeDataString(envelopeKey)}", ct);
|
|
if (!res.IsSuccessStatusCode)
|
|
return null;
|
|
return await res.Content.ReadAsByteArrayAsync(ct);
|
|
}
|
|
|
|
// ?? Annotation / Sign / Reject ???????????????????????????????????
|
|
|
|
/// <summary>
|
|
/// Returns the signature placeholders the authenticated receiver must sign.
|
|
/// </summary>
|
|
public async Task<List<SignatureElementDto>> GetSignatureElementsAsync(CancellationToken ct = default)
|
|
{
|
|
try
|
|
{
|
|
return await _http.GetFromJsonAsync<List<SignatureElementDto>>("api/annotation/elements", ct)
|
|
?? new List<SignatureElementDto>();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Failed to fetch signature elements.");
|
|
return new List<SignatureElementDto>();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Submits the signed envelope using the Blazor-friendly endpoint.
|
|
/// </summary>
|
|
public async Task<HttpStatusCode> SignBlazorAsync(BlazorSignaturePayload payload, CancellationToken ct = default)
|
|
{
|
|
var res = await _http.PostAsJsonAsync("api/annotation/blazor", payload, ct);
|
|
return res.StatusCode;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Fetches the TOTP QR code + registration deadline for the given
|
|
/// envelope-receiver key (encoded uuid+signature). The API generates
|
|
/// a fresh secret on first call and persists it server-side.
|
|
/// </summary>
|
|
public async Task<(TfaRegistrationResponse? Data, HttpStatusCode Status)> GetTfaRegistrationAsync(string envelopeReceiverId, CancellationToken ct = default)
|
|
{
|
|
try
|
|
{
|
|
var res = await _http.GetAsync($"api/tfa/{Uri.EscapeDataString(envelopeReceiverId)}", ct);
|
|
if (!res.IsSuccessStatusCode)
|
|
return (null, res.StatusCode);
|
|
var data = await res.Content.ReadFromJsonAsync<TfaRegistrationResponse>(cancellationToken: ct);
|
|
return (data, res.StatusCode);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Failed to fetch TFA registration for {Key}", envelopeReceiverId);
|
|
return (null, HttpStatusCode.InternalServerError);
|
|
}
|
|
}
|
|
|
|
/// <summary>Submits the signed annotations payload. Returns the HTTP status code.</summary>
|
|
public async Task<HttpStatusCode> SignAsync<TPayload>(TPayload? payload, CancellationToken ct = default)
|
|
{
|
|
var res = payload is null
|
|
? await _http.PostAsync("api/annotation", content: null, ct)
|
|
: await _http.PostAsJsonAsync("api/annotation", payload, ct);
|
|
return res.StatusCode;
|
|
}
|
|
|
|
public async Task<bool> RejectAsync(string reason, CancellationToken ct = default)
|
|
{
|
|
var res = await _http.PostAsJsonAsync("api/annotation/reject", reason, ct);
|
|
return res.IsSuccessStatusCode;
|
|
}
|
|
|
|
// ?? Read-only share ??????????????????????????????????????????????
|
|
|
|
public async Task<bool> ShareReadOnlyAsync(ReadOnlyShareRequest req, CancellationToken ct = default)
|
|
{
|
|
var res = await _http.PostAsJsonAsync("api/readonly", req, ct);
|
|
return res.IsSuccessStatusCode;
|
|
}
|
|
|
|
// ?? Auth (logout) ????????????????????????????????????????????????
|
|
|
|
public async Task<bool> LogoutAsync(CancellationToken ct = default)
|
|
{
|
|
var res = await _http.PostAsync("auth/logout", content: null, ct);
|
|
return res.IsSuccessStatusCode;
|
|
}
|
|
|
|
// ?? Localization ?????????????????????????????????????????????????
|
|
|
|
public async Task<Dictionary<string, string>?> GetLocalizationStringsAsync(CancellationToken ct = default)
|
|
{
|
|
try
|
|
{
|
|
return await _http.GetFromJsonAsync<Dictionary<string, string>>("api/Localization", ct);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Failed to fetch localization strings.");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public async Task SetLanguageAsync(string language, CancellationToken ct = default)
|
|
{
|
|
try
|
|
{
|
|
await _http.PostAsync($"api/Localization/lang?language={Uri.EscapeDataString(language)}", content: null, ct);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Failed to set language to {Lang}.", language);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the currently selected language code (e.g. "de", "en"), or
|
|
/// <c>null</c> if no language cookie has been set yet (the API answers
|
|
/// with HTTP 404 in that case).
|
|
/// </summary>
|
|
public async Task<string?> GetLanguageAsync(CancellationToken ct = default)
|
|
{
|
|
try
|
|
{
|
|
var res = await _http.GetAsync("api/Localization/lang", ct);
|
|
if (!res.IsSuccessStatusCode)
|
|
return null;
|
|
return (await res.Content.ReadAsStringAsync(ct))?.Trim('"');
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Failed to read current language.");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// ?? Helpers ??????????????????????????????????????????????????????
|
|
|
|
private async Task<ReceiverAuthResponse?> GetAuthAsync(string url, CancellationToken ct)
|
|
{
|
|
var res = await _http.GetAsync(url, ct);
|
|
return await ReadAuthAsync(res, ct);
|
|
}
|
|
|
|
private async Task<ReceiverAuthResponse?> PostAuthAsync<TReq>(string url, TReq body, CancellationToken ct)
|
|
{
|
|
var res = await _http.PostAsJsonAsync(url, body, ct);
|
|
return await ReadAuthAsync(res, ct);
|
|
}
|
|
|
|
private static async Task<ReceiverAuthResponse?> ReadAuthAsync(HttpResponseMessage res, CancellationToken ct)
|
|
{
|
|
// ReceiverAuthController returns a ReceiverAuthResponse body for both
|
|
// 2xx and known error statuses (401/404/500). We always try to deserialize.
|
|
try
|
|
{
|
|
return await res.Content.ReadFromJsonAsync<ReceiverAuthResponse>(cancellationToken: ct);
|
|
}
|
|
catch
|
|
{
|
|
return new ReceiverAuthResponse
|
|
{
|
|
Status = ReceiverAuthStatus.Error,
|
|
ErrorMessage = $"Unexpected response ({(int)res.StatusCode})."
|
|
};
|
|
}
|
|
}
|
|
}
|