using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; using System.Net.Http; using System.Net.Http.Headers; using Newtonsoft.Json.Linq; namespace AyaNovaQBI { internal class util { #region API stuff public const string TEST_ROUTE = "notify/hello"; public const string API_BASE_ROUTE = "api/v8/"; private const int MAX_TRIES = 3;//max times to retry an api call before giving up private const int API_RETRY_DELAY = 3000;//pause in ms before retrying api call public static int HTTPCLIENT_TIMEOUT_SECONDS = 100;//changed by the setting in ops anyway, just a in-case sensible default here public static HttpClient client = null; //url once known to be good internal static string ApiBaseUrl { get; set; } //auth processes url for api and this is the best guess as to the client url to use for notification / help links etc internal static string GuessClientUrl { get; set; } internal static string JWT { get; set; } public static void InitClient() { if (client != null) { client.Dispose(); client = null; } client = new HttpClient(); client.Timeout = TimeSpan.FromSeconds(HTTPCLIENT_TIMEOUT_SECONDS); } /// /// Only a return value of "OK" is ok /// /// /// public static async Task InitAndConfirmAddressAsync(string serverUrl) { ApiBaseUrl = serverUrl; InitClient(); try { // TimeSpan tsDefault = client.Timeout; // client.Timeout = new TimeSpan(0, 0, 20); HttpResponseMessage response = await client.GetAsync(serverUrl + TEST_ROUTE); // client.Timeout = tsDefault; if (response.IsSuccessStatusCode) return "OK"; else return "Failed: " + response.StatusCode.ToString(); } catch (Exception ex) { while (ex.InnerException != null) ex = ex.InnerException; return "Failed exception: \r\n" + ex.Message; } } public async static Task AuthenticateAsync(string login, string password = null) { InitClient(); if (password == null) password = login; dynamic creds = new JObject(); creds.login = login; creds.password = password; var requestMessage = new HttpRequestMessage(HttpMethod.Post, ApiBaseUrl + "auth"); requestMessage.Content = new StringContent(creds.ToString(), System.Text.Encoding.UTF8, "application/json"); HttpResponseMessage response = null; try { response = await client.SendAsync(requestMessage); } catch (HttpRequestException ex) { var Err = ex.Message; var InnerErr = ""; if (ex.InnerException != null) InnerErr = ex.InnerException.Message; return false; // throw new Exception("POST error, route: " + route + "\r\nError:" + Err + "\r\nInner error:" + InnerErr + "\r\nStack:" + ex.StackTrace + "\r\nPOSTED OBJECT:\r\n" + postJson); } //ApiResponse a = await PostAsync("auth", creds.ToString()); if (response.IsSuccessStatusCode) { var a= new ApiResponse() { HttpResponse = response, ObjectResponse = Parse(await response.Content.ReadAsStringAsync()) }; bool tfa = a.ObjectResponse["data"]["tfa"].Value(); if (tfa == true) { //get 2fa code and send it in tfa t = new tfa(); if(t.ShowDialog()==System.Windows.Forms.DialogResult.Cancel) return false; } JWT = a.ObjectResponse["data"]["token"].Value(); return true; } return false; } public async static Task GetAsync(string route) { Exception FirstException = null; for (int x = 0; x < MAX_TRIES; x++) { try { return await TryGetAsync(route); } catch (Exception ex) { if (FirstException == null) FirstException = ex; } await Task.Delay(API_RETRY_DELAY); } //no luck re-throw the exception throw new Exception("API call failed after " + MAX_TRIES.ToString() + " attempts", FirstException); } private async static Task TryGetAsync(string route) { var requestMessage = new HttpRequestMessage(HttpMethod.Get, ApiBaseUrl + route); if (!string.IsNullOrWhiteSpace(JWT)) requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", JWT); HttpResponseMessage response = null; try { response = await client.SendAsync(requestMessage); } catch (HttpRequestException ex) { var Err = ex.Message; var InnerErr = ""; if (ex.InnerException != null) InnerErr = ex.InnerException.Message; throw new Exception("GET error, route: " + route + "\r\nError:" + Err + "\r\nInner error:" + InnerErr + "\r\nStack:" + ex.StackTrace); } var responseAsString = await response.Content.ReadAsStringAsync(); if (!response.IsSuccessStatusCode) { throw new Exception("GET error, code: " + (int)response.StatusCode + ", route: " + route + "\r\n" + responseAsString + "\r\n" + response.ReasonPhrase); } else return new ApiResponse() { HttpResponse = response, ObjectResponse = Parse(responseAsString) }; } public async static Task PostAsync(string route, dynamic d) { Exception FirstException = null; for (int x = 0; x < MAX_TRIES; x++) { try { return await TryPostAsync(route, d.ToString(Newtonsoft.Json.Formatting.None)); } catch (Exception ex) { if (FirstException == null) FirstException = ex; } await Task.Delay(API_RETRY_DELAY); } //no luck re-throw the exception throw new Exception("API call failed after " + MAX_TRIES.ToString() + " attempts", FirstException); } public async static Task PostAsync(string route, string s = null) { Exception FirstException = null; for (int x = 0; x < MAX_TRIES; x++) { try { return await TryPostAsync(route, s); } catch (Exception ex) { if (FirstException == null) FirstException = ex; } await Task.Delay(API_RETRY_DELAY); } //no luck re-throw the exception throw new Exception("API call failed after " + MAX_TRIES.ToString() + " attempts", FirstException); } internal async static Task TryPostAsync(string route, string postJson = null) { var requestMessage = new HttpRequestMessage(HttpMethod.Post, ApiBaseUrl + route); if (!string.IsNullOrWhiteSpace(JWT)) requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", JWT); if (!string.IsNullOrWhiteSpace(postJson)) requestMessage.Content = new StringContent(postJson, System.Text.Encoding.UTF8, "application/json"); HttpResponseMessage response = null; try { response = await client.SendAsync(requestMessage); } catch (HttpRequestException ex) { var Err = ex.Message; var InnerErr = ""; if (ex.InnerException != null) InnerErr = ex.InnerException.Message; throw new Exception("POST error, route: " + route + "\r\nError:" + Err + "\r\nInner error:" + InnerErr + "\r\nStack:" + ex.StackTrace + "\r\nPOSTED OBJECT:\r\n" + postJson); } var responseAsString = await response.Content.ReadAsStringAsync(); if (!response.IsSuccessStatusCode) { if (string.IsNullOrWhiteSpace(postJson)) postJson = "n/a"; throw new Exception("POST error, code: " + (int)response.StatusCode + ", route: " + route + "\r\n" + responseAsString + "\r\n" + response.ReasonPhrase + "\r\nPOSTED OBJECT:\r\n" + postJson); } else return new ApiResponse() { HttpResponse = response, ObjectResponse = Parse(responseAsString) }; } /// /// /// /// /// public static JObject Parse(string jsonString) { if (string.IsNullOrWhiteSpace(jsonString)) { return null; } return JObject.Parse(jsonString); } public static long IdFromResponse(ApiResponse a) { return a.ObjectResponse["data"]["id"].Value(); } public static uint CTokenFromResponse(ApiResponse a) { return a.ObjectResponse["data"]["concurrency"].Value(); } public async static Task PutAsync(string route, dynamic d) { Exception FirstException = null; for (int x = 0; x < MAX_TRIES; x++) { try { return await TryPutAsync(route, d.ToString(Newtonsoft.Json.Formatting.None)); } catch (Exception ex) { if (FirstException == null) FirstException = ex; } await Task.Delay(API_RETRY_DELAY); } //no luck re-throw the exception throw new Exception("API call failed after " + MAX_TRIES.ToString() + " attempts", FirstException); } public async static Task PutAsync(string route) { Exception FirstException = null; for (int x = 0; x < MAX_TRIES; x++) { try { return await TryPutAsync(route); } catch (Exception ex) { if (FirstException == null) FirstException = ex; } await Task.Delay(API_RETRY_DELAY); } //no luck re-throw the exception throw new Exception("API call failed after " + MAX_TRIES.ToString() + " attempts", FirstException); } public async static Task TryPutAsync(string route, string putJson = null) { var requestMessage = new HttpRequestMessage(HttpMethod.Put, ApiBaseUrl + route); if (!string.IsNullOrWhiteSpace(JWT)) requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", JWT); if (!string.IsNullOrWhiteSpace(putJson)) requestMessage.Content = new StringContent(putJson, System.Text.Encoding.UTF8, "application/json"); HttpResponseMessage response = null; try { response = await client.SendAsync(requestMessage); } catch (HttpRequestException ex) { var Err = ex.Message; var InnerErr = ""; if (ex.InnerException != null) InnerErr = ex.InnerException.Message; throw new Exception("PUT error, route: " + route + "\r\nError:" + Err + "\r\nInner error:" + InnerErr + "\r\nStack:" + ex.StackTrace + "\r\nPOSTED OBJECT:\r\n" + putJson); } var responseAsString = await response.Content.ReadAsStringAsync(); if (!response.IsSuccessStatusCode) { if (string.IsNullOrWhiteSpace(putJson)) putJson = "n/a"; throw new Exception("PUT error, code: " + (int)response.StatusCode + ", route: " + route + "\r\n" + responseAsString + "\r\n" + response.ReasonPhrase + "\r\nPUT OBJECT:\r\n" + putJson); } else return new ApiResponse() { HttpResponse = response, ObjectResponse = Parse(responseAsString) }; } public class ApiResponse { public HttpResponseMessage HttpResponse { get; set; } public JObject ObjectResponse { get; set; } public string CompactResponse { get { return ObjectResponse.ToString(Newtonsoft.Json.Formatting.None); } } } #endregion #region QB STUFF public static List GetInvoiceableItems() { var random = new Random(); var l = new List(); for (int i = 1; i < random.Next(25, 100); i++) l.Add(new InvoiceableItem { Customer = $"Customer {random.Next(1, 5)}", Linked = random.Next(2) == 1, Project = $"project {i}", ServiceDate = DateTime.Now.ToString("g"), ServiceNumber = (40 + i).ToString(), Status = $"Waiting to be invoiced", StatusColor = "FF00FFAA", WorkorderId = 4 }); return l.OrderBy(x => x.Customer) .ThenBy(x => x.ServiceNumber) .ThenBy(x => x.ServiceDate) .ToList(); } public static async Task InitializeQBI() { //COPY most of this code from qbi v7 becuase it has a lot of edge cases in it and it's complex and thorough, but break it into abstracted bits so can be replicated in other accounting add-on's more easily //This is pre-pfc block of stuff that doesn't map well from v7 qbi plugin to here so replicate it in spirit here but not much is copyable just the concepts //LOGIN to v8 first auth d = new auth(); if (d.ShowDialog() == System.Windows.Forms.DialogResult.Cancel) return false; //Fetch AyaNova license //Check if licensed //check if build date is within licensed date (how did I do that automated build date thing?) // copy timestamp.cs and the createtimestamp.exe utility from v7 qbi and create build event to run it here and do same thing //check that AyaNova version matches required minimum for this QBI ( //PFC - Util.QBValidate stuff: //Validate QB connection can be made and open connection and start session with QB (see Util.QBValidate in v7 //once connected collect the country, version we are dealing with (Util.qbValidate) //confirm qb is 2008 or newer and bail if not (util.qbvalidate) //cache company name and other qb info //PFC - PopulateQBListCache() //PFC - PopulateAyaListCache() //PFC - integration object check (fetch or create if not present) (Util.integrationObjectCheck()) //PFC - Validate settings, create if necessary (Util.ValidateSettings()) and save //PFC - verify integration mapped objects still exist at each end (Util.PreFlightCheck() line 199) //DONE return true; } #endregion } }