Idle detection
Be notified when the user goes idle (no input for a threshold) or the screen locks, via IIdleDetector (the Idle Detection API) — e.g. to auto-lock a session, pause a sync, or update presence in a collaborative app. WASM-only: the idle-detection permission needs a live gesture and the detector needs the live document.
RequestPermissionAsync() must run from a gesture; WatchAsync(onChange, thresholdSeconds) then pushes an IdleReading on each user/screen state change. The spec enforces a 60-second minimum threshold. Dispose the handle to stop.
IdleDetectorDemo.cs
using Rask.Site;
using Rask.Wasm.Browser;
namespace Rask.Site.Features;
/// <summary>
/// <see cref="IIdleDetector" /> — request the <c>idle-detection</c> permission from a gesture, then
/// watch for the user going idle or the screen locking. WASM-only: permission needs a live gesture and
/// the detector needs the live document.
/// </summary>
public sealed partial class IdleDetectorDemo(IIdleDetector idle) : Component, IAsyncDisposable
{
private IAsyncDisposable? _watch;
private string _user = "active";
private string _screen = "unlocked";
private string _status = "(idle)";
protected override Component? Render() =>
Div.Class($"{Tw.Card} shadow-sm border-0")[
Div.Class(Tw.CardBody)[
Button.Class($"{Tw.BtnPrimary} mb-3").Id("idle-start").OnClickAsync(Start)[
"Start watching (60s threshold)"],
Div.Class("text-sm text-ui-muted")["User: ", Code.Id("idle-user")[_user]],
Div.Class("text-sm text-ui-muted")["Screen: ", Code.Id("idle-screen")[_screen]],
Div.Class("text-sm text-ui-muted")["Status: ", Code.Id("idle-status")[_status]]
]
];
private async Task Start()
{
if (_watch is not null)
{
return;
}
try
{
if (!await idle.IsSupportedAsync())
{
_status = "Idle Detection not supported in this browser";
return;
}
if (await idle.RequestPermissionAsync() != "granted")
{
_status = "Permission denied";
return;
}
_watch = await idle.WatchAsync(reading =>
{
_user = reading.UserIdle ? "idle" : "active";
_screen = reading.ScreenLocked ? "locked" : "unlocked";
StateHasChanged();
return Task.CompletedTask;
});
_status = "Watching — stop interacting for 60s to go idle";
}
catch (Exception ex)
{
_status = "Failed: " + ex.Message;
}
}
public async ValueTask DisposeAsync()
{
if (_watch is not null)
{
await _watch.DisposeAsync();
}
}
}
Live result
User:
activeScreen:
unlockedStatus:
(idle)