using InABox.Core; using InABox.Core.Postable; using InABox.DynamicGrid; using InABox.Poster.Shared; using Microsoft.Web.WebView2.Wpf; using MYOB.AccountRight.SDK; using MYOB.AccountRight.SDK.Contracts; using MYOB.AccountRight.SDK.Services; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Web; using System.Windows; using System.Windows.Controls; using System.Windows.Threading; namespace InABox.Poster.MYOB; public class MYOBConnectionData(ApiConfiguration configuration, CompanyFileService cfService, IOAuthKeyService authKey) { public ApiConfiguration Configuration { get; set; } = configuration; public CompanyFileService CompanyFileService { get; set; } = cfService; public CompanyFile? CompanyFile { get; set; } public CompanyFileCredentials? CompanyFileCredentials { get; set; } public CompanyFileWithResources? ActiveCompanyFile { get; set; } public IOAuthKeyService AuthKey { get; set; } = authKey; } public static partial class MYOBPosterEngine { internal static readonly string DEV_KEY = "f3b27f88-2ef9-4d8e-95c1-d0a045d0afee"; internal static readonly string SECRET_KEY = "ksm0e8yo6oumcPb63A8cduaN"; internal const string AUTH_URL = "https://secure.myob.com/oauth2/account/authorize"; internal const string AUTH_SCOPE = "CompanyFile"; internal const string REDIRECT_URL = "http://desktop"; private static MYOBConnectionData? _connectionData; public static bool GetAuthorisationCode(IApiConfiguration config, out string? code) { var url = $"{AUTH_URL}?client_id={config.ClientId}&redirect_uri={HttpUtility.UrlEncode(config.RedirectUrl)}&scope={AUTH_SCOPE}&response_type=code"; var window = new Window { Width = 800, Height = 600, Title = "Sign in to MYOB" }; string? resultCode = null; var view = new WebView2 { }; view.NavigationCompleted += (o, e) => { view.ExecuteScriptAsync("document.documentElement.innerHTML;").ContinueWith(task => { if (task.Result is null) return; var str = Serialization.Deserialize(task.Result); if (str is null) return; var match = CodeRegex().Match(str); if (!match.Success) return; resultCode = match.Groups[1].Value; window.Dispatcher.BeginInvoke(() => { if(window.DialogResult is null) { window.DialogResult = true; window.Close(); } }); }); }; view.Source = new Uri(url); window.Content = view; if(window.ShowDialog() == true) { code = resultCode; return true; } else { code = null; return false; } } public static MYOBConnectionData? GetConnectionDataOrNull() { return _connectionData; } public static MYOBConnectionData GetConnectionData() { if(_connectionData is MYOBConnectionData data) { return _connectionData; } var configuration = new ApiConfiguration(DEV_KEY, SECRET_KEY, REDIRECT_URL); var authService = new OAuthService(configuration); if(!GetAuthorisationCode(configuration, out var code)) { throw new PostCancelledException(); } if(code is null) { throw new PostFailedMessageException("No authorisation code was received."); } var keystore = new SimpleOAuthKeyService { OAuthResponse = authService.GetTokens(code) }; var cfService = new CompanyFileService(configuration, null, keystore); _connectionData = new MYOBConnectionData(configuration, cfService, keystore); return _connectionData; } [GeneratedRegex("code=(.*)<")] private static partial Regex CodeRegex(); } public abstract class MYOBPosterEngine : BasePosterEngine, IGlobalSettingsPosterEngine where TPostable : Entity, IPostable, IRemotable, IPersistent, new() where TSettings : MYOBPosterSettings, new() where TPoster : class, IMYOBPoster { private MYOBGlobalPosterSettings GetGlobalSettings() => (this as IGlobalSettingsPosterEngine).GetGlobalSettings(); private void SaveGlobalSettings(MYOBGlobalPosterSettings settings) => (this as IGlobalSettingsPosterEngine).SaveGlobalSettings(settings); protected override TPoster CreatePoster() { var poster = base.CreatePoster(); poster.Script = GetScriptDocument(); return poster; } public override bool BeforePost(IDataModel model) { return Poster.BeforePost(model); } protected void LoadConnectionData() { var data = MYOBPosterEngine.GetConnectionData(); var globalSettings = GetGlobalSettings(); if(data.CompanyFile is null || data.CompanyFile.Id != globalSettings.CompanyFile.ID) { CompanyFile? file; if(globalSettings.CompanyFile.ID == Guid.Empty) { file = MYOBCompanyFileSelectionDialog.SelectCompanyFile(); if(file is null) { throw new PostCancelledException(); } else { globalSettings.CompanyFile.ID = file.Id; globalSettings.CompanyFile.Name = file.Name; SaveGlobalSettings(globalSettings); globalSettings.CommitChanges(); } } if(!globalSettings.NoCredentials && globalSettings.CompanyFileUserID.IsNullOrWhiteSpace()) { var credentials = new MYOBCompanyFileCredentials { UserID = globalSettings.CompanyFileUserID, Password = globalSettings.CompanyFilePassword, NoCredentials = globalSettings.NoCredentials }; if (DynamicGridUtils.EditObject(credentials, customiseGrid: grid => { grid.OnValidate += (grid, items, errors) => { var item = items.FirstOrDefault(); if (item is null) return; if(!item.NoCredentials && item.UserID.IsNullOrWhiteSpace()) { errors.Add("[UserID] cannot be blank"); } }; })) { globalSettings.NoCredentials = credentials.NoCredentials; globalSettings.CompanyFileUserID = credentials.UserID; globalSettings.CompanyFilePassword = credentials.Password; SaveGlobalSettings(globalSettings); globalSettings.CommitChanges(); } else { throw new PostCancelledException(); } } var companyFile = data.CompanyFileService.GetRange().FirstOrDefault(x => x.Id == globalSettings.CompanyFile.ID); var fileCredentials = new CompanyFileCredentials(globalSettings.CompanyFileUserID, globalSettings.CompanyFilePassword); data.CompanyFile = companyFile; data.CompanyFileCredentials = fileCredentials; // data.ActiveCompanyFile = data.CompanyFileService.Get(companyFile, fileCredentials); } Poster.ConnectionData = data; } protected override IPostResult DoProcess(IDataModel model) { LoadConnectionData(); return Poster.Process(model); } public override void AfterPost(IDataModel model, IPostResult result) { } }