| File: CookieStore.cs | Web Access |
| Project: Microsoft.AspNetCore.Components.Platform.csproj (Microsoft.AspNetCore.Components.Platform) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics.CodeAnalysis; using Microsoft.AspNetCore.Internal; using Microsoft.JSInterop; namespace Microsoft.AspNetCore.Components.Platform; /// <summary> /// Represents the browser Cookie Store API for the current page. /// </summary> public sealed class CookieStore : IInternalAsyncDisposal { private readonly IJSRuntime _jsRuntime; private Task<IJSObjectReference>? _referenceTask; private bool _disposed; internal CookieStore(IJSRuntime jsRuntime) { _jsRuntime = jsRuntime; } /// <summary> /// Gets the first cookie with the specified name. /// </summary> /// <param name="name">The name of the cookie to retrieve.</param> /// <returns>The first cookie named <paramref name="name"/>, or <see langword="null"/> if no such cookie exists.</returns> /// <exception cref="ArgumentNullException"><paramref name="name"/> is <see langword="null"/>.</exception> /// <exception cref="NotSupportedException">The browser does not provide the Cookie Store API for the current page.</exception> public async ValueTask<CookieListItem?> GetAsync(string name) { ArgumentNullException.ThrowIfNull(name); var cookieStore = await GetReferenceAsync().ConfigureAwait(false); return await cookieStore.InvokeAsync<CookieListItem?>("get", name).ConfigureAwait(false); } /// <summary> /// Gets the first cookie that matches the specified options. /// </summary> /// <param name="options">The options that select the cookie.</param> /// <returns>The first matching cookie, or <see langword="null"/> if no cookie matches.</returns> /// <exception cref="ArgumentNullException"><paramref name="options"/> is <see langword="null"/>.</exception> /// <exception cref="NotSupportedException">The browser does not provide the Cookie Store API for the current page.</exception> [DynamicDependency(LinkerFlags.JsonSerialized, typeof(CookieStoreGetOptions))] public async ValueTask<CookieListItem?> GetAsync(CookieStoreGetOptions options) { ArgumentNullException.ThrowIfNull(options); var cookieStore = await GetReferenceAsync().ConfigureAwait(false); return await cookieStore.InvokeAsync<CookieListItem?>("get", options).ConfigureAwait(false); } /// <summary> /// Gets all cookies visible to the current page. /// </summary> /// <returns>The cookies visible to the current page.</returns> /// <exception cref="NotSupportedException">The browser does not provide the Cookie Store API for the current page.</exception> [DynamicDependency(LinkerFlags.JsonSerialized, typeof(CookieListItem))] public async ValueTask<IReadOnlyList<CookieListItem>> GetAllAsync() { var cookieStore = await GetReferenceAsync().ConfigureAwait(false); return await cookieStore.InvokeAsync<CookieListItem[]>("getAll").ConfigureAwait(false); } /// <summary> /// Gets all cookies with the specified name. /// </summary> /// <param name="name">The name of the cookies to retrieve.</param> /// <returns>The cookies named <paramref name="name"/>.</returns> /// <exception cref="ArgumentNullException"><paramref name="name"/> is <see langword="null"/>.</exception> /// <exception cref="NotSupportedException">The browser does not provide the Cookie Store API for the current page.</exception> [DynamicDependency(LinkerFlags.JsonSerialized, typeof(CookieListItem))] public async ValueTask<IReadOnlyList<CookieListItem>> GetAllAsync(string name) { ArgumentNullException.ThrowIfNull(name); var cookieStore = await GetReferenceAsync().ConfigureAwait(false); return await cookieStore.InvokeAsync<CookieListItem[]>("getAll", name).ConfigureAwait(false); } /// <summary> /// Gets all cookies that match the specified options. /// </summary> /// <param name="options">The options that select the cookies.</param> /// <returns>The matching cookies.</returns> /// <exception cref="ArgumentNullException"><paramref name="options"/> is <see langword="null"/>.</exception> /// <exception cref="NotSupportedException">The browser does not provide the Cookie Store API for the current page.</exception> [DynamicDependency(LinkerFlags.JsonSerialized, typeof(CookieStoreGetOptions))] [DynamicDependency(LinkerFlags.JsonSerialized, typeof(CookieListItem))] public async ValueTask<IReadOnlyList<CookieListItem>> GetAllAsync(CookieStoreGetOptions options) { ArgumentNullException.ThrowIfNull(options); var cookieStore = await GetReferenceAsync().ConfigureAwait(false); return await cookieStore.InvokeAsync<CookieListItem[]>("getAll", options).ConfigureAwait(false); } /// <summary> /// Sets the unpartitioned cookie with the specified name and value for the host of the current page and the path <c>"/"</c>, /// replacing any existing unpartitioned cookie with the same name, host, and path. /// </summary> /// <param name="name">The name of the cookie.</param> /// <param name="value">The value of the cookie.</param> /// <returns>A <see cref="ValueTask"/> representing the completion of the operation.</returns> /// <exception cref="ArgumentNullException"><paramref name="name"/> or <paramref name="value"/> is <see langword="null"/>.</exception> /// <exception cref="NotSupportedException">The browser does not provide the Cookie Store API for the current page.</exception> public async ValueTask SetAsync(string name, string value) { ArgumentNullException.ThrowIfNull(name); ArgumentNullException.ThrowIfNull(value); var cookieStore = await GetReferenceAsync().ConfigureAwait(false); await cookieStore.InvokeVoidAsync("set", name, value).ConfigureAwait(false); } /// <summary> /// Sets a cookie with the specified options, replacing any existing cookie with the same name, domain, and path that has the same partitioning. /// </summary> /// <param name="options">The name, value, and attributes of the cookie.</param> /// <returns>A <see cref="ValueTask"/> representing the completion of the operation.</returns> /// <exception cref="ArgumentNullException"><paramref name="options"/> is <see langword="null"/>.</exception> /// <exception cref="NotSupportedException">The browser does not provide the Cookie Store API for the current page.</exception> [DynamicDependency(LinkerFlags.JsonSerialized, typeof(CookieInit))] public async ValueTask SetAsync(CookieInit options) { ArgumentNullException.ThrowIfNull(options); var cookieStore = await GetReferenceAsync().ConfigureAwait(false); await cookieStore.InvokeVoidAsync("set", options).ConfigureAwait(false); } /// <summary> /// Deletes the unpartitioned cookie with the specified name that is restricted to the host of the current page and has the path <c>"/"</c>, if it exists. /// A partitioned cookie, or a cookie with another domain or path, is not deleted. /// </summary> /// <param name="name">The name of the cookie to delete.</param> /// <returns>A <see cref="ValueTask"/> representing the completion of the operation.</returns> /// <exception cref="ArgumentNullException"><paramref name="name"/> is <see langword="null"/>.</exception> /// <exception cref="NotSupportedException">The browser does not provide the Cookie Store API for the current page.</exception> public async ValueTask DeleteAsync(string name) { ArgumentNullException.ThrowIfNull(name); var cookieStore = await GetReferenceAsync().ConfigureAwait(false); await cookieStore.InvokeVoidAsync("delete", name).ConfigureAwait(false); } /// <summary> /// Deletes the cookie that matches the specified options, if it exists. /// </summary> /// <param name="options">The name and attributes of the cookie to delete.</param> /// <returns>A <see cref="ValueTask"/> representing the completion of the operation.</returns> /// <exception cref="ArgumentNullException"><paramref name="options"/> is <see langword="null"/>.</exception> /// <exception cref="NotSupportedException">The browser does not provide the Cookie Store API for the current page.</exception> [DynamicDependency(LinkerFlags.JsonSerialized, typeof(CookieStoreDeleteOptions))] public async ValueTask DeleteAsync(CookieStoreDeleteOptions options) { ArgumentNullException.ThrowIfNull(options); var cookieStore = await GetReferenceAsync().ConfigureAwait(false); await cookieStore.InvokeVoidAsync("delete", options).ConfigureAwait(false); } async ValueTask IInternalAsyncDisposal.InternalDisposeAsync() { if (_disposed) { return; } _disposed = true; // Don't wait for a pending acquisition. It could hold up scope disposal until the JS interop timeout. if (_referenceTask is { IsCompletedSuccessfully: true } referenceTask) { await referenceTask.Result.DisposeAsync().ConfigureAwait(false); } } private Task<IJSObjectReference> GetReferenceAsync() { ObjectDisposedException.ThrowIf(_disposed, this); if (_referenceTask is null || _referenceTask.IsFaulted || _referenceTask.IsCanceled) { _referenceTask = AcquireReferenceAsync(); } return _referenceTask; } private async Task<IJSObjectReference> AcquireReferenceAsync() { const string NotAvailableMessage = "The Cookie Store API is not available for the current page."; IJSObjectReference? reference; try { reference = await _jsRuntime.GetValueAsync<IJSObjectReference?>("cookieStore").ConfigureAwait(false); } catch (JSException ex) { // Interop throws for a missing window.cookieStore, such as on an insecure page or in an older browser. throw new NotSupportedException(NotAvailableMessage, ex); } return reference ?? throw new NotSupportedException(NotAvailableMessage); } }