Skip to content

Commit

Permalink
feat(butil): add document.cookie support to Butil #6352 (#6354)
Browse files Browse the repository at this point in the history
  • Loading branch information
msynk authored Dec 20, 2023
1 parent b707910 commit 03e7db3
Show file tree
Hide file tree
Showing 8 changed files with 266 additions and 1 deletion.
80 changes: 80 additions & 0 deletions src/Butil/Bit.Butil.Demo/Pages/CookiePage.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
@page "/cookie"
@inject Bit.Butil.Console console
@inject Bit.Butil.Cookie cookie

<PageTitle>Cookie Samples</PageTitle>

<h1>Cookie</h1>

<pre style="font-family:Consolas">
@@inject Bit.Butil.Cookie cookie

@@code {
...
await Cookie.Remove("cookie-name");
...
}
</pre>

<br />
<hr />

<h3>Open the DevTools console and click on buttons</h3>

<hr />
<br />

<button @onclick=GetAllCookies>All cookies</button>

<br />
<br />

<input @bind="getCookieName" />
<button @onclick=GetCookie>GetCookie</button>

<br />
<br />
<br />

<span>Name:</span>
<input @bind="setCookieName" />
<br/>
<span>Value:</span>
<input @bind="setCookieValue" />
<br />
<button @onclick=SetCookie>SetCookie</button>

<br />
<br />
<br />

<input @bind="removeCookieName" />
<button @onclick=RemoveCookie>RemoveCookie</button>

@code {
private string getCookieName = "";
private string setCookieName = "";
private string setCookieValue = "";
private string removeCookieName = "";

private async Task GetAllCookies()
{
await console.Log("All cookies =", string.Join<ButilCookie>("; ", await cookie.GetAll()));
}

private async Task GetCookie()
{
await console.Log("GetCookie =", await cookie.Get(getCookieName));
}

private async Task SetCookie()
{
await cookie.Set(new ButilCookie { Name = setCookieName, Value = setCookieValue });
await console.Log("SetCookie =", await cookie.Get(setCookieName));
}

private async Task RemoveCookie()
{
await cookie.Remove(removeCookieName);
}
}
3 changes: 2 additions & 1 deletion src/Butil/Bit.Butil.Demo/Shared/Header.razor
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<a href="/navigator">Navigator</a> |
<a href="/storage">Storage</a> |
<a href="/location">Location</a> |
<a href="/screen">Screen</a>
<a href="/screen">Screen</a> |
<a href="/cookie">Cookie</a>
</div>
<hr />
1 change: 1 addition & 0 deletions src/Butil/Bit.Butil/BitButil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public static IServiceCollection AddBitButilServices(this IServiceCollection ser
services.AddScoped<SessionStorage>();
services.AddScoped<Location>();
services.AddScoped<Screen>();
services.AddScoped<Cookie>();

return services;
}
Expand Down
36 changes: 36 additions & 0 deletions src/Butil/Bit.Butil/Internals/JsInterops/CookieJsInterop.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.JSInterop;

namespace Bit.Butil;

internal static class CookieJsInterop
{
internal static async Task<ButilCookie[]> CookieGetAll(this IJSRuntime js)
{
var cookie = await js.InvokeAsync<string>("BitButil.cookie.get");
return cookie.Split(';').Select(ButilCookie.Parse).ToArray();
}

internal static async Task<ButilCookie?> CookieGet(this IJSRuntime js, string name)
{
var allCookies = await CookieGetAll(js);
return allCookies.FirstOrDefault(c => c.Name == name);
}

internal static async Task CookieRemove(this IJSRuntime js, string name)
{
var cookie = new ButilCookie { Name = name, MaxAge = 0, Expires = null };
await CookieSet(js, cookie);
}

internal static async Task CookieRemove(this IJSRuntime js, ButilCookie cookie)
{
cookie.MaxAge = 0;
cookie.Expires = null;
await CookieSet(js, cookie);
}

internal static async Task CookieSet(this IJSRuntime js, ButilCookie cookie)
=> await js.InvokeVoidAsync("BitButil.cookie.set", cookie.ToString());
}
43 changes: 43 additions & 0 deletions src/Butil/Bit.Butil/Publics/Cookie.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System.Threading.Tasks;
using Microsoft.JSInterop;

namespace Bit.Butil;

/// <summary>
/// The Document property cookie lets you read and write cookies associated with the document.
/// It serves as a getter and setter for the actual values of the cookies.
/// <br />
/// More info: <see href="https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie">https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie</see>
/// </summary>
public class Cookie(IJSRuntime js)
{
/// <summary>
/// Gets all cookies registered on the current document.
/// </summary>
public async Task<ButilCookie[]> GetAll()
=> await js.CookieGetAll();

/// <summary>
/// Returns a cookie by providing the cookie name.
/// </summary>
public async Task<ButilCookie?> Get(string name)
=> await js.CookieGet(name);

/// <summary>
/// Removes a cookie by providing the its name.
/// </summary>
public async Task Remove(string name)
=> await js.CookieRemove(name);

/// <summary>
/// Removes a cookie.
/// </summary>
public async Task Remove(ButilCookie cookie)
=> await js.CookieRemove(cookie);

/// <summary>
/// Sets a cookie.
/// </summary>
public async Task Set(ButilCookie cookie)
=> await js.CookieSet(cookie);
}
75 changes: 75 additions & 0 deletions src/Butil/Bit.Butil/Publics/Cookie/ButilCookie.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using System;
using System.Text;

namespace Bit.Butil;

public class ButilCookie
{
public string? Name { get; set; }
public string? Value { get; set; }
public string? Domain { get; set; }
public DateTimeOffset? Expires { get; set; }
public long? MaxAge { get; set; }
public bool Partitioned { get; set; }
public string? Path { get; set; }
public SameSite? SameSite { get; set; }
public bool Secure { get; set; }

public override string ToString()
{
if (Name is null) return string.Empty;

var sb = new StringBuilder();

sb.Append($"{Name}={Value}");

if (Domain is not null)
{
sb.Append($";domain={Domain}");
}

if (Expires is not null)
{
sb.Append($";expires={Expires.Value.UtcDateTime.ToString("ddd, MMM dd yyyy HH:mm:ss \"GMT\"")}");
}

if (MaxAge is not null)
{
sb.Append($";max-age={MaxAge}");
}

if (Partitioned)
{
sb.Append(";partitioned");
}

if (Path is not null)
{
sb.Append($";path={Path}");
}

if (SameSite is not null)
{
sb.Append($";samesite={SameSite.ToString()!.ToLowerInvariant()}");
}

if (Secure)
{
sb.Append(";secure");
}

return sb.ToString();
}

public static ButilCookie Parse(string rawCookie)
{
var cookie = new ButilCookie();
if (rawCookie.Contains('='))
{
var split = rawCookie.Split('=');
cookie.Name = split[0].Trim();
cookie.Value = split[1].Trim();
}
return cookie;
}
}
21 changes: 21 additions & 0 deletions src/Butil/Bit.Butil/Publics/Cookie/SameSite.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace Bit.Butil;

public enum SameSite
{
/// <summary>
/// Explicitly states no restrictions will be applied.
/// The cookie will be sent in all requests—both cross-site and same-site.
/// </summary>
None,

/// <summary>
/// Send the cookie for all same-site requests and top-level navigation GET requests.
/// </summary>
Lax,

/// <summary>
/// Prevent the cookie from being sent by the browser to the target site in
/// all cross-site browsing contexts, even when following a regular link.
/// </summary>
Strict,
}
8 changes: 8 additions & 0 deletions src/Butil/Bit.Butil/Scripts/cookie.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
var BitButil = BitButil || {};

(function (butil: any) {
butil.cookie = {
get() { return document.cookie },
set(cookie: string) { document.cookie = cookie },
};
}(BitButil));

0 comments on commit 03e7db3

Please sign in to comment.