diff --git a/Adspace/ExchangeManager.cs b/Adspace/ExchangeManager.cs index af4f778f..fb6e8b69 100644 --- a/Adspace/ExchangeManager.cs +++ b/Adspace/ExchangeManager.cs @@ -562,7 +562,7 @@ private List Request(Url url, Ad wrappedAd) // Get and impression/error URLs included with this wrap XmlNode errorUrlNode = wrapper.SelectSingleNode("./Error"); - if (errorUrlNode != null) + if (errorUrlNode != null && !string.IsNullOrEmpty(errorUrlNode.InnerText)) { ad.ErrorUrls.Add(errorUrlNode.InnerText.Trim()); } @@ -708,7 +708,7 @@ private List Request(Url url, Ad wrappedAd) // Get and impression/error URLs included with this wrap XmlNode errorUrlNode = inlineNode.SelectSingleNode("./Error"); - if (errorUrlNode != null) + if (errorUrlNode != null && !string.IsNullOrEmpty(errorUrlNode.InnerText)) { string errorUrl = errorUrlNode.InnerText.Trim(); if (errorUrl != "about:blank") @@ -920,6 +920,8 @@ private void ReportError(List urls, int errorCode) { foreach (string url in urls) { + if (string.IsNullOrEmpty(url)) continue; + try { // Macros diff --git a/Helpers/CefsharpLifespanHandler.cs b/Helpers/CefsharpLifespanHandler.cs new file mode 100644 index 00000000..21b2a427 --- /dev/null +++ b/Helpers/CefsharpLifespanHandler.cs @@ -0,0 +1,35 @@ +/** + * Copyright (C) 2024 Xibo Signage Ltd + * + * Xibo - Digital Signage - http://www.xibo.org.uk + * + * This file is part of Xibo. + * + * Xibo is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * Xibo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Xibo. If not, see . + */ +using CefSharp; + +namespace XiboClient.Helpers +{ + public class CefsharpLifespanHandler : CefSharp.Handler.LifeSpanHandler + { + protected override bool OnBeforePopup(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, string targetUrl, string targetFrameName, WindowOpenDisposition targetDisposition, bool userGesture, IPopupFeatures popupFeatures, IWindowInfo windowInfo, IBrowserSettings browserSettings, ref bool noJavascriptAccess, out IWebBrowser newBrowser) + { + newBrowser = null; + + // Return true to cancel the popup creation + return true; + } + } +} diff --git a/Helpers/XiboRequestHandler.cs b/Helpers/XiboRequestHandler.cs index cd92dac2..1a60661b 100644 --- a/Helpers/XiboRequestHandler.cs +++ b/Helpers/XiboRequestHandler.cs @@ -33,10 +33,10 @@ public XiboRequestHandler(bool isConfigureProxy) _isConfigureProxy = isConfigureProxy; } - protected override void OnRenderProcessTerminated(IWebBrowser chromiumWebBrowser, IBrowser browser, CefTerminationStatus status) + protected override void OnRenderProcessTerminated(IWebBrowser chromiumWebBrowser, IBrowser browser, CefTerminationStatus status, int errorCode, string errorString) { // If the render process crashed, we should just log. - Trace.WriteLine(new LogMessage("XiboRequestHandler", "OnRenderProcessTerminate: a cef sub process has terminated. " + status.ToString()), LogType.Error.ToString()); + Trace.WriteLine(new LogMessage("XiboRequestHandler", "OnRenderProcessTerminate: a cef sub process has terminated. " + status.ToString() + ", message: " + errorString), LogType.Error.ToString()); } protected override bool GetAuthCredentials(IWebBrowser chromiumWebBrowser, IBrowser browser, string originUrl, bool isProxy, string host, int port, string realm, string scheme, IAuthCallback callback) diff --git a/Logic/ApplicationSettings.cs b/Logic/ApplicationSettings.cs index af230ed9..39ae2f4f 100644 --- a/Logic/ApplicationSettings.cs +++ b/Logic/ApplicationSettings.cs @@ -1,5 +1,5 @@ /** - * Copyright (C) 2023 Xibo Signage Ltd + * Copyright (C) 2024 Xibo Signage Ltd * * Xibo - Digital Signage - http://www.xibo.org.uk * @@ -52,9 +52,9 @@ private static readonly Lazy /// private List ExcludedProperties; - public string ClientVersion { get; } = "4 R402.1"; + public string ClientVersion { get; } = "4 R404.1"; public string Version { get; } = "7"; - public int ClientCodeVersion { get; } = 402; + public int ClientCodeVersion { get; } = 404; private ApplicationSettings() { diff --git a/Logic/HardwareKey.cs b/Logic/HardwareKey.cs index b502c7a5..36980c44 100644 --- a/Logic/HardwareKey.cs +++ b/Logic/HardwareKey.cs @@ -17,6 +17,7 @@ * You should have received a copy of the GNU Affero General Public License * along with Xibo. If not, see . */ +using Newtonsoft.Json; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Generators; using Org.BouncyCastle.OpenSsl; @@ -29,12 +30,15 @@ using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; +using System.Text; +using System.Threading; namespace XiboClient { class HardwareKey { private static object _locker = new object(); + private static string operatingSystemJson = string.Empty; private static AsymmetricCipherKeyPair _keys; private string _hardwareKey; @@ -301,5 +305,28 @@ public static string LocalIPAddress() .FirstOrDefault(ip => ip.AddressFamily == AddressFamily.InterNetwork) .ToString(); } + + public static string OperatingSystemAsJson() + { + if (string.IsNullOrEmpty(operatingSystemJson)) + { + StringBuilder sb = new StringBuilder(); + using (StringWriter sw = new StringWriter(sb)) + using (JsonWriter writer = new JsonTextWriter(sw)) + { + writer.Formatting = Formatting.None; + writer.WriteStartObject(); + writer.WritePropertyName("version"); + writer.WriteValue(Environment.OSVersion.Platform.ToString()); + writer.WritePropertyName("sdk"); + writer.WriteValue(Environment.OSVersion.Version.ToString()); + writer.WriteEndObject(); + } + + operatingSystemJson = sb.ToString(); + } + + return operatingSystemJson; + } } } diff --git a/OptionsForm.xaml.cs b/OptionsForm.xaml.cs index d8da9ece..f3e689e6 100644 --- a/OptionsForm.xaml.cs +++ b/OptionsForm.xaml.cs @@ -216,7 +216,7 @@ private void Button_Connect_Click(object sender, RoutedEventArgs e) "windows", ApplicationSettings.Default.ClientVersion, ApplicationSettings.Default.ClientCodeVersion, - Environment.OSVersion.ToString(), + HardwareKey.OperatingSystemAsJson(), this.hardwareKey.MacAddress, this.hardwareKey.Channel, this.hardwareKey.getXmrPublicKey(), @@ -410,7 +410,7 @@ private async void AuthCodeTimer_Elapsed(object sender, System.Timers.ElapsedEve "windows", ApplicationSettings.Default.ClientVersion, ApplicationSettings.Default.ClientCodeVersion, - Environment.OSVersion.ToString(), + HardwareKey.OperatingSystemAsJson(), this.hardwareKey.MacAddress, this.hardwareKey.Channel, this.hardwareKey.getXmrPublicKey(), diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs index 97008f22..e65ae134 100644 --- a/Properties/AssemblyInfo.cs +++ b/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Xibo Digital Signage")] [assembly: AssemblyProduct("Xibo")] -[assembly: AssemblyCopyright("Copyright © Xibo Signage Ltd 2023")] +[assembly: AssemblyCopyright("Copyright © Xibo Signage Ltd 2024")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -49,6 +49,6 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("4.402.1.0")] -[assembly: AssemblyFileVersion("4.402.1.0")] +[assembly: AssemblyVersion("4.404.1.0")] +[assembly: AssemblyFileVersion("4.404.1.0")] [assembly: Guid("3bd467a4-4ef9-466a-b156-a79c13a863f7")] diff --git a/Rendering/Layout.xaml.cs b/Rendering/Layout.xaml.cs index 38a7cf8f..8f8b5c71 100644 --- a/Rendering/Layout.xaml.cs +++ b/Rendering/Layout.xaml.cs @@ -1,5 +1,5 @@ /** - * Copyright (C) 2022 Xibo Signage Ltd + * Copyright (C) 2024 Xibo Signage Ltd * * Xibo - Digital Signage - http://www.xibo.org.uk * diff --git a/Rendering/Region.xaml.cs b/Rendering/Region.xaml.cs index 8b9617dd..bd80f01f 100644 --- a/Rendering/Region.xaml.cs +++ b/Rendering/Region.xaml.cs @@ -746,6 +746,8 @@ private void StopMedia(Media media) // Call any error urls. foreach (string url in media.AdspaceExchangeErrorUrls) { + if (string.IsNullOrEmpty(url)) continue; + try { // Macros @@ -756,7 +758,10 @@ private void StopMedia(Media media) // Call the URL new Flurl.Url(uri).WithTimeout(10).GetAsync().ContinueWith(t => { - LogMessage.Error("Region", "StopMedia", "failed to report error to " + uri); + if (t.Exception != null) + { + LogMessage.Error("Region", "StopMedia", "failed to report error to " + uri + ", e: " + t.Exception.Message); + } }, TaskContinuationOptions.OnlyOnFaulted); } diff --git a/Rendering/Video.cs b/Rendering/Video.cs index f66d0a7c..5dcb6931 100644 --- a/Rendering/Video.cs +++ b/Rendering/Video.cs @@ -1,5 +1,5 @@ /** - * Copyright (C) 2023 Xibo Signage Ltd + * Copyright (C) 2024 Xibo Signage Ltd * * Xibo - Digital Signage - https://xibosignage.com * @@ -139,7 +139,15 @@ private void MediaElement_MediaOpened(object sender, RoutedEventArgs e) if (_duration == 0) { // Add the duration of the video - watchmanTtl = watchmanTtl.Add(this.mediaElement.NaturalDuration.TimeSpan); + if (this.mediaElement.NaturalDuration == System.Windows.Duration.Automatic) + { + // This is strange, so we will just log and keep the watchman duration at 60 seconds + LogMessage.Audit("Video", "MediaElement_MediaOpened", "Duration not detected on open"); + } + else + { + watchmanTtl = watchmanTtl.Add(this.mediaElement.NaturalDuration.TimeSpan); + } } else { @@ -217,16 +225,6 @@ private void MediaElement_Loaded(object sender, RoutedEventArgs e) { Trace.WriteLine(new LogMessage("Video", "MediaElement_Loaded: " + this.Id + " Control loaded, calling Play."), LogType.Audit.ToString()); - try - { - this.mediaElement.Play(); - } - catch (Exception ex) - { - // Problem calling play, we should expire. - Trace.WriteLine(new LogMessage("Video", "MediaElement_Loaded: " + this.Id + " Media Failed. E = " + ex.Message), LogType.Error.ToString()); - } - // We make a watchman to check that the video actually gets loaded. _StartWatchman = new DispatcherTimer { Interval = TimeSpan.FromSeconds(ApplicationSettings.Default.VideoStartTimeout) }; _StartWatchman.Tick += (timerSender, args) => @@ -248,6 +246,20 @@ private void MediaElement_Loaded(object sender, RoutedEventArgs e) }; _StartWatchman.Start(); + + // Actually play the video + try + { + this.mediaElement.Play(); + } + catch (Exception ex) + { + // Problem calling play, we should expire. + Trace.WriteLine(new LogMessage("Video", "MediaElement_Loaded: " + this.Id + " Media Failed. E = " + ex.Message), LogType.Error.ToString()); + + // Cancel the watchman + _StartWatchman.Stop(); + } } #endregion diff --git a/Rendering/WebCef.cs b/Rendering/WebCef.cs index 0cec8af6..f9122c84 100644 --- a/Rendering/WebCef.cs +++ b/Rendering/WebCef.cs @@ -1,5 +1,5 @@ /** - * Copyright (C) 2021 Xibo Signage Ltd + * Copyright (C) 2024 Xibo Signage Ltd * * Xibo - Digital Signage - http://www.xibo.org.uk * @@ -60,6 +60,7 @@ public override void RenderMedia(double position) Name = "region_" + this.regionId }; webView.RequestContext = new CefSharp.RequestContext(requestContextSettings); + webView.LifeSpanHandler = new CefsharpLifespanHandler(); // Configure run time CEF settings? CefSharp.Cef.UIThreadTaskFactory.StartNew(() => @@ -72,14 +73,14 @@ public override void RenderMedia(double position) // NTLM/Auth Server White Lists. if (!string.IsNullOrEmpty(ApplicationSettings.Default.AuthServerWhitelist)) { - if (!webView.RequestContext.SetPreference("auth.server_whitelist", ApplicationSettings.Default.AuthServerWhitelist, out string error)) + if (!webView.RequestContext.SetPreference("auth.server_allowlist", ApplicationSettings.Default.AuthServerWhitelist, out string error)) { - Trace.WriteLine(new LogMessage("WebCef", "RenderMedia: auth.server_whitelist. e = " + error), LogType.Info.ToString()); + Trace.WriteLine(new LogMessage("WebCef", "RenderMedia: auth.server_allowlist. e = " + error), LogType.Info.ToString()); } - if (!webView.RequestContext.SetPreference("auth.negotiate_delegate_whitelist", ApplicationSettings.Default.AuthServerWhitelist, out string error2)) + if (!webView.RequestContext.SetPreference("auth.negotiate_delegate_allowlist", ApplicationSettings.Default.AuthServerWhitelist, out string error2)) { - Trace.WriteLine(new LogMessage("WebCef", "RenderMedia: auth.negotiate_delegate_whitelist. e = " + error2), LogType.Info.ToString()); + Trace.WriteLine(new LogMessage("WebCef", "RenderMedia: auth.negotiate_delegate_allowlist. e = " + error2), LogType.Info.ToString()); } } } diff --git a/Rendering/WebEdge.cs b/Rendering/WebEdge.cs index b24bcca5..82a32851 100644 --- a/Rendering/WebEdge.cs +++ b/Rendering/WebEdge.cs @@ -1,5 +1,5 @@ /** - * Copyright (C) 2023 Xibo Signage Ltd + * Copyright (C) 2024 Xibo Signage Ltd * * Xibo - Digital Signage - http://www.xibo.org.uk * @@ -76,6 +76,16 @@ private async void InitialiseWebView() // Environment CoreWebView2EnvironmentOptions environmentOptions; + // Where should we store user data? + string userDataFolder = ApplicationSettings.Default.LibraryPath; + + // Workaround for paths which do not have a trailing slash and are therefore not detected as absolute + // e.g. E: + if (!userDataFolder.EndsWith("\\") && !userDataFolder.EndsWith("/")) + { + userDataFolder += "\\"; + } + // NTLM/Auth Server White Lists. if (!string.IsNullOrEmpty(ApplicationSettings.Default.AuthServerWhitelist)) { @@ -98,8 +108,9 @@ private async void InitialiseWebView() await this.webView.EnsureCoreWebView2Async( await CoreWebView2Environment.CreateAsync( null, - ApplicationSettings.Default.LibraryPath, - environmentOptions)); + userDataFolder, + environmentOptions + )); // Proxy // Not yet supported https://github.com/MicrosoftEdge/WebView2Feedback/issues/132 @@ -107,6 +118,10 @@ await CoreWebView2Environment.CreateAsync( { }*/ + + // Console logs + this.webView.CoreWebView2.GetDevToolsProtocolEventReceiver("Log.entryAdded").DevToolsProtocolEventReceived += OnConsoleMessage; + await this.webView.CoreWebView2.CallDevToolsProtocolMethodAsync("Log.enable", "{}"); } /// @@ -135,6 +150,7 @@ private void WebView_CoreWebView2InitializationCompleted(object sender, Microsof if (e.IsSuccess) { webView.CoreWebView2.Settings.IsPinchZoomEnabled = isPinchToZoomEnabled; + webView.CoreWebView2.Settings.IsStatusBarEnabled = false; _webViewInitialised = true; } else @@ -270,5 +286,18 @@ protected override string MakeHtmlSubstitutions(string cachedFile) html += ""; return html; } + + /// + /// Log console messages + /// + /// + /// + private void OnConsoleMessage(object sender, CoreWebView2DevToolsProtocolEventReceivedEventArgs e) + { + if (e != null && e.ParameterObjectAsJson != null) + { + Trace.WriteLine("WebView2:" + e.ParameterObjectAsJson); + } + } } } diff --git a/XiboClient.csproj b/XiboClient.csproj index dd266c3e..2bbc726e 100644 --- a/XiboClient.csproj +++ b/XiboClient.csproj @@ -132,6 +132,7 @@ + @@ -299,11 +300,11 @@ - - 1.8.9 + + 2.4.0 - 115.3.110 + 126.2.70 1.2.0 @@ -333,7 +334,7 @@ 160.1000.6 - 1.0.1938.49 + 1.0.2592.51 4.0.1.13 @@ -342,7 +343,7 @@ 13.0.3 - 3.1.9 + 3.1.11 5.0.1 diff --git a/XmdsAgents/DataAgent.cs b/XmdsAgents/DataAgent.cs index e35e8d8b..b8fe872a 100644 --- a/XmdsAgents/DataAgent.cs +++ b/XmdsAgents/DataAgent.cs @@ -28,6 +28,7 @@ using Swan; using System.IO; using XiboClient.Log; +using System.Web.Services.Protocols; namespace XiboClient.XmdsAgents { @@ -142,52 +143,61 @@ public void Run() { if (widget.ForceUpdate || !widget.IsUpToDate) { - // Download using XMDS GetResource - using (xmds.xmds xmds = new xmds.xmds()) + try { - xmds.Credentials = null; - xmds.UseDefaultCredentials = true; + // Download using XMDS GetResource + using (xmds.xmds xmds = new xmds.xmds()) + { + xmds.Credentials = null; + xmds.UseDefaultCredentials = true; - xmds.Url = ApplicationSettings.Default.XiboClient_xmds_xmds + "&method=getData"; - string result = xmds.GetData(ApplicationSettings.Default.ServerKey, ApplicationSettings.Default.HardwareKey, widget.WidgetId); + xmds.Url = ApplicationSettings.Default.XiboClient_xmds_xmds + "&method=getData"; + string result = xmds.GetData(ApplicationSettings.Default.ServerKey, ApplicationSettings.Default.HardwareKey, widget.WidgetId); - // Write the result to disk - using (FileStream fileStream = File.Open(widget.Path, FileMode.Create, FileAccess.Write, FileShare.Read)) - { - using (StreamWriter sw = new StreamWriter(fileStream)) + // Write the result to disk + using (FileStream fileStream = File.Open(widget.Path, FileMode.Create, FileAccess.Write, FileShare.Read)) { - sw.Write(result); - sw.Close(); + using (StreamWriter sw = new StreamWriter(fileStream)) + { + sw.Write(result); + sw.Close(); + } } - } - // Clear the force update flag if set. - widget.UpdatedDt = DateTime.Now; - widget.ForceUpdate = false; + // Clear the force update flag if set. + widget.UpdatedDt = DateTime.Now; + widget.ForceUpdate = false; - // Load the result into a JSON response. - try - { - JObject json = JsonConvert.DeserializeObject(result); - if (json != null && json.ContainsKey("files")) + // Load the result into a JSON response. + try { - foreach (JObject file in json.GetValueOrDefault("files").Cast()) + JObject json = JsonConvert.DeserializeObject(result); + if (json != null && json.ContainsKey("files")) { - // Make a new fileagent somehow, to download this file. - OnNewHttpRequiredFile?.Invoke( - int.Parse(file.GetValue("id").ToString()), - double.Parse(file.GetValue("size").ToString()), - file.GetValue("md5").ToString(), - file.GetValue("saveAs").ToString(), - file.GetValue("path").ToString() - ); + foreach (JObject file in json.GetValueOrDefault("files").Cast()) + { + // Make a new fileagent somehow, to download this file. + OnNewHttpRequiredFile?.Invoke( + int.Parse(file.GetValue("id").ToString()), + double.Parse(file.GetValue("size").ToString()), + file.GetValue("md5").ToString(), + file.GetValue("saveAs").ToString(), + file.GetValue("path").ToString() + ); + } } } + catch (Exception ex) + { + // TODO: mark as errored + LogMessage.Error("DataAgent", "Run", "Unable to parse JSON result. e = " + ex.Message); + } } - catch (Exception ex) - { - LogMessage.Error("DataAgent", "Run", "Unable to parse JSON result. e = " + ex.Message); - } + } + catch (SoapHeaderException ex) + { + // TODO: mark as errored + LogMessage.Error("DataAgent", "Run", "Unable to get data for widgetId = " + widget.WidgetId + ", e = " + ex.Message); } } diff --git a/XmdsAgents/RegisterAgent.cs b/XmdsAgents/RegisterAgent.cs index a03e70ad..61e58c76 100644 --- a/XmdsAgents/RegisterAgent.cs +++ b/XmdsAgents/RegisterAgent.cs @@ -1,6 +1,6 @@ -/* +/** * Xibo - Digital Signage - http://www.xibo.org.uk - * Copyright (C) 2020 Xibo Signage Ltd + * Copyright (C) 2024 Xibo Signage Ltd * * This file is part of Xibo. * @@ -244,7 +244,7 @@ private string callRegister(xmds.xmds xmds, HardwareKey key) "windows", ApplicationSettings.Default.ClientVersion, ApplicationSettings.Default.ClientCodeVersion, - Environment.OSVersion.ToString(), + HardwareKey.OperatingSystemAsJson(), key.MacAddress, key.Channel, key.getXmrPublicKey(),