Files
Christopher Britton 28877a23e0 Add OS notifications for identities requiring authentication (#949)
* Add OS notifications for identities requiring authorization

  Shows toast notifications when identities need ext-auth or MFA. Single
  events include an action button (Authenticate for ext-auth). Multiple
  events arriving within a 5-second window are batched into a single
  summary notification to avoid spam.

* Fix notification throttle not re-firing when required

* Clean up notification throttle

* Move summary format to notification throttle constructor to clean up message queue signature

* update release notes

* Suppress auth notifications when app is visible, exclude disabled identities from notification count, update release notes
2026-04-01 13:52:34 -04:00

101 lines
3.8 KiB
C#

/*
Copyright NetFoundry Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Threading;
using Microsoft.Toolkit.Uwp.Notifications;
namespace Ziti.Desktop.Edge.Utils {
/// <summary>
/// Collects identity authorization notifications (ext-auth, MFA, etc.) over a short time window.
/// If only one arrives, it sends the specific notification. If multiple arrive, it sends a single summary instead.
/// </summary>
public class NotificationThrottle {
private readonly Action<string, string, ToastButton> _sendNotification;
private readonly Dictionary<string, Action> _pendingNotifications = new Dictionary<string, Action>();
private readonly DispatcherTimer _throttleTimer;
private readonly string _header;
private readonly string _summaryFormat;
private bool _suppress;
public bool Suppress {
get { return _suppress; }
set { _suppress = value; }
}
public NotificationThrottle(Action<string, string, ToastButton> sendNotification, string header, string summaryFormat) {
_sendNotification = sendNotification;
_header = header;
_summaryFormat = summaryFormat;
_throttleTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(5) };
_throttleTimer.Tick += (s, e) => SendPendingNotifications();
}
/// <summary>
/// Adds a notification to the pending queue. Skips duplicates for the same identity.
/// Resets the 5-second window each time a new notification arrives.
/// </summary>
public void Queue(string identityIdentifier, string message, ToastButton button) {
if (_pendingNotifications.ContainsKey(identityIdentifier)) return;
_pendingNotifications[identityIdentifier] = () => _sendNotification(_header, message, button);
_throttleTimer.Stop();
_throttleTimer.Start();
}
/// <summary>
/// Removes a single identity from the seen set so it can trigger notifications again.
/// </summary>
public void Remove(string identityIdentifier) {
_pendingNotifications.Remove(identityIdentifier);
}
/// <summary>
/// Resets all state. Called on service disconnect so notifications re-fire on reconnect.
/// </summary>
public void Clear() {
_throttleTimer.Stop();
_pendingNotifications.Clear();
}
/// <summary>
/// Fires when the throttle window expires. Sends the individual notification or a summary if count > 1.
/// </summary>
private void SendPendingNotifications() {
_throttleTimer.Stop();
int count = _pendingNotifications.Count;
if (count == 0) return;
if (_suppress) {
_pendingNotifications.Clear();
return;
}
bool sendSummary = count > 1;
if (sendSummary) {
_sendNotification(_header, string.Format(_summaryFormat, count), null);
} else {
// Send the single notification
_pendingNotifications.Values.First()();
}
_pendingNotifications.Clear();
}
}
}