From 4ec62a4bfa6d27c84f77d7fdb0656993e8bdd88d Mon Sep 17 00:00:00 2001 From: Zac Marcus Date: Wed, 2 Sep 2026 17:39:37 +1000 Subject: [PATCH] Add optional CancellationToken support to XeroClient async methods Adds support for a CancellationToken, the parameter is appended last with a default value on both IXeroClient and XeroClient, so existing callers shouldn't need to be changed. --- .../src/Client/IXeroClient.cs | 17 +++---- .../src/Client/XeroClient.cs | 47 +++++++++++-------- 2 files changed, 37 insertions(+), 27 deletions(-) diff --git a/Xero.NetStandard.OAuth2Client/src/Client/IXeroClient.cs b/Xero.NetStandard.OAuth2Client/src/Client/IXeroClient.cs index ba1daa01..589a74c4 100644 --- a/Xero.NetStandard.OAuth2Client/src/Client/IXeroClient.cs +++ b/Xero.NetStandard.OAuth2Client/src/Client/IXeroClient.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Xero.NetStandard.OAuth2.Config; using Xero.NetStandard.OAuth2.Models; @@ -15,13 +16,13 @@ public interface IXeroClient string BuildLoginUriPkce(string codeVerifier); string BuildLoginUriPkce(string codeVerifier, string state); string BuildLoginUriPkce(string codeVerifier, string state, string scope); - Task RequestAccessTokenAsync(string code); - Task RequestAccessTokenPkceAsync(string code, string codeVerifier); - Task RefreshAccessTokenAsync(IXeroToken xeroToken); - Task RequestClientCredentialsTokenAsync(bool fetchTenants=true); - Task GetCurrentValidTokenAsync(IXeroToken xeroToken); - Task> GetConnectionsAsync(IXeroToken xeroToken); - Task DeleteConnectionAsync(IXeroToken xeroToken, Tenant xeroTenant); - Task RevokeAccessTokenAsync(IXeroToken xeroToken); + Task RequestAccessTokenAsync(string code, CancellationToken cancellationToken = default); + Task RequestAccessTokenPkceAsync(string code, string codeVerifier, CancellationToken cancellationToken = default); + Task RefreshAccessTokenAsync(IXeroToken xeroToken, CancellationToken cancellationToken = default); + Task RequestClientCredentialsTokenAsync(bool fetchTenants = true, CancellationToken cancellationToken = default); + Task GetCurrentValidTokenAsync(IXeroToken xeroToken, CancellationToken cancellationToken = default); + Task> GetConnectionsAsync(IXeroToken xeroToken, CancellationToken cancellationToken = default); + Task DeleteConnectionAsync(IXeroToken xeroToken, Tenant xeroTenant, CancellationToken cancellationToken = default); + Task RevokeAccessTokenAsync(IXeroToken xeroToken, CancellationToken cancellationToken = default); } } \ No newline at end of file diff --git a/Xero.NetStandard.OAuth2Client/src/Client/XeroClient.cs b/Xero.NetStandard.OAuth2Client/src/Client/XeroClient.cs index 36824eba..8c39c095 100644 --- a/Xero.NetStandard.OAuth2Client/src/Client/XeroClient.cs +++ b/Xero.NetStandard.OAuth2Client/src/Client/XeroClient.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using System.Net.Http; using System.Net.Http.Headers; @@ -133,8 +134,9 @@ public string BuildLoginUriPkce(string codeVerifier, string state, string scope) /// Refreshes your current token /// /// + /// /// - public async Task RefreshAccessTokenAsync(IXeroToken xeroToken) + public async Task RefreshAccessTokenAsync(IXeroToken xeroToken, CancellationToken cancellationToken = default) { if (xeroToken == null) { @@ -147,7 +149,7 @@ public async Task RefreshAccessTokenAsync(IXeroToken xeroToken) ClientId = xeroConfiguration.ClientId, ClientSecret = xeroConfiguration.ClientSecret, RefreshToken = xeroToken.RefreshToken - }); + }, cancellationToken); if (response.IsError) { @@ -165,8 +167,10 @@ public async Task RefreshAccessTokenAsync(IXeroToken xeroToken) /// /// Requests a fully formed IXeroToken with list of tenants filled /// + /// + /// /// - public async Task RequestClientCredentialsTokenAsync(bool fetchTenants=true) + public async Task RequestClientCredentialsTokenAsync(bool fetchTenants = true, CancellationToken cancellationToken = default) { var response = await _httpClient.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest @@ -175,7 +179,7 @@ public async Task RequestClientCredentialsTokenAsync(bool fetchTenan ClientId = xeroConfiguration.ClientId, ClientSecret = xeroConfiguration.ClientSecret, Scope = xeroConfiguration.Scope - }); + }, cancellationToken); if (response.IsError) { @@ -188,7 +192,7 @@ public async Task RequestClientCredentialsTokenAsync(bool fetchTenan ExpiresAtUtc = DateTime.UtcNow.AddSeconds(response.ExpiresIn) }; if(fetchTenants){ - xeroToken.Tenants = await GetConnectionsAsync(xeroToken); + xeroToken.Tenants = await GetConnectionsAsync(xeroToken, cancellationToken); } return xeroToken; @@ -198,8 +202,9 @@ public async Task RequestClientCredentialsTokenAsync(bool fetchTenan /// Requests a fully formed IXeroToken with list of tenants filled /// /// Code returned from callback + /// /// - public async Task RequestAccessTokenAsync(string code) + public async Task RequestAccessTokenAsync(string code, CancellationToken cancellationToken = default) { var response = await _httpClient.RequestAuthorizationCodeTokenAsync(new AuthorizationCodeTokenRequest { @@ -213,7 +218,7 @@ public async Task RequestAccessTokenAsync(string code) { { "scope", xeroConfiguration.Scope} } - }); + }, cancellationToken); if (response.IsError) { @@ -227,7 +232,7 @@ public async Task RequestAccessTokenAsync(string code) ExpiresAtUtc = DateTime.UtcNow.AddSeconds(response.ExpiresIn), IdToken = response.IdentityToken, }; - xeroToken.Tenants = await GetConnectionsAsync(xeroToken); + xeroToken.Tenants = await GetConnectionsAsync(xeroToken, cancellationToken); return xeroToken; } @@ -238,9 +243,9 @@ public async Task RequestAccessTokenAsync(string code) /// /// code from callback /// codeVerifier used for initial request - /// + /// /// - public async Task RequestAccessTokenPkceAsync(string code, string codeVerifier) + public async Task RequestAccessTokenPkceAsync(string code, string codeVerifier, CancellationToken cancellationToken = default) { var response = await _httpClient.RequestAuthorizationCodeTokenAsync(new AuthorizationCodeTokenRequest @@ -256,7 +261,7 @@ public async Task RequestAccessTokenPkceAsync(string code, string co { "scope", xeroConfiguration.Scope} }, CodeVerifier = codeVerifier - }); + }, cancellationToken); if (response.IsError) { @@ -277,12 +282,13 @@ public async Task RequestAccessTokenPkceAsync(string code, string co /// Convenience method to refresh token for you if it is expired /// /// your current XeroToken + /// /// - public async Task GetCurrentValidTokenAsync(IXeroToken xeroToken) + public async Task GetCurrentValidTokenAsync(IXeroToken xeroToken, CancellationToken cancellationToken = default) { if (DateTime.UtcNow > xeroToken.ExpiresAtUtc) { - return await RefreshAccessTokenAsync(xeroToken); + return await RefreshAccessTokenAsync(xeroToken, cancellationToken); } return xeroToken; @@ -291,14 +297,15 @@ public async Task GetCurrentValidTokenAsync(IXeroToken xeroToken) /// Get's a list of Tokens given the accesstoken /// /// + /// /// List of Tenants attached to accesstoken - public async Task> GetConnectionsAsync(IXeroToken xeroToken) + public async Task> GetConnectionsAsync(IXeroToken xeroToken, CancellationToken cancellationToken = default) { using (var requestMessage = new HttpRequestMessage(HttpMethod.Get, $"{xeroConfiguration.XeroApiBaseUri}/connections")) { requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", xeroToken.AccessToken); - var result = await _httpClient.SendAsync(requestMessage); + var result = await _httpClient.SendAsync(requestMessage, cancellationToken); var json = await result.Content.ReadAsStringAsync(); if (result.StatusCode == System.Net.HttpStatusCode.OK) { @@ -315,14 +322,15 @@ public async Task> GetConnectionsAsync(IXeroToken xeroToken) /// /// /// + /// /// List of Tenants attached to accesstoken - public async Task DeleteConnectionAsync(IXeroToken xeroToken, Tenant xeroTenant) + public async Task DeleteConnectionAsync(IXeroToken xeroToken, Tenant xeroTenant, CancellationToken cancellationToken = default) { using (var requestMessage = new HttpRequestMessage(HttpMethod.Delete, $"{xeroConfiguration.XeroApiBaseUri}/connections" + "/" + xeroTenant.id)) { requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", xeroToken.AccessToken); - var result = await _httpClient.SendAsync(requestMessage); + var result = await _httpClient.SendAsync(requestMessage, cancellationToken); if (result.StatusCode == System.Net.HttpStatusCode.NoContent) { return; @@ -336,8 +344,9 @@ public async Task DeleteConnectionAsync(IXeroToken xeroToken, Tenant xeroTenant) /// Revokes the current token - immediate disconnect all orgs and stops the user authorisation /// /// + /// /// - public async Task RevokeAccessTokenAsync(IXeroToken xeroToken) + public async Task RevokeAccessTokenAsync(IXeroToken xeroToken, CancellationToken cancellationToken = default) { if (xeroToken == null) { @@ -349,7 +358,7 @@ public async Task RevokeAccessTokenAsync(IXeroToken xeroToken) ClientId = xeroConfiguration.ClientId, ClientSecret = xeroConfiguration.ClientSecret, Token = xeroToken.RefreshToken - }); + }, cancellationToken); if (response.IsError) {