using Microsoft.JSInterop; namespace DnsmasqWebUI.Extensions; /// /// Safe JS interop helpers that swallow disconnect/cancel/dispose exceptions. /// Use for best-effort or teardown calls; use raw InvokeVoidAsync/InvokeAsync when failure must propagate. /// public static class JSRuntimeExtensions { /// /// Invokes a void JS function without throwing on circuit disconnect, cancel, or dispose. /// Returns true if the call succeeded, false otherwise. /// public static async ValueTask InvokeVoidAsyncSafe( this IJSRuntime js, string identifier, CancellationToken cancellationToken = default, params object?[]? args) { try { var a = args ?? []; if (cancellationToken == default) await js.InvokeVoidAsync(identifier, a); else await js.InvokeVoidAsync(identifier, cancellationToken, a); return true; } catch (JSDisconnectedException) { return false; } catch (TaskCanceledException) { return false; } catch (ObjectDisposedException) { return false; } } /// /// Invokes a JS function that returns a value, without throwing on circuit disconnect, cancel, or dispose. /// Returns default when the call fails (e.g. circuit gone). /// public static async ValueTask InvokeAsyncSafe( this IJSRuntime js, string identifier, CancellationToken cancellationToken = default, params object?[]? args) { try { var a = args ?? []; if (cancellationToken == default) return await js.InvokeAsync(identifier, a); return await js.InvokeAsync(identifier, cancellationToken, a); } catch (JSDisconnectedException) { return default; } catch (TaskCanceledException) { return default; } catch (ObjectDisposedException) { return default; } } }