From ac67c472f9f70cc60e749283a9a6fc32e5f65fe6 Mon Sep 17 00:00:00 2001 From: vnugent Date: Sat, 17 Aug 2024 22:35:53 -0400 Subject: update glance widget --- back-end/src/Endpoints/WidgetEndpoint.cs | 123 +++++++++++++++++++++++++------ 1 file changed, 99 insertions(+), 24 deletions(-) diff --git a/back-end/src/Endpoints/WidgetEndpoint.cs b/back-end/src/Endpoints/WidgetEndpoint.cs index d72030b..ad2fa39 100644 --- a/back-end/src/Endpoints/WidgetEndpoint.cs +++ b/back-end/src/Endpoints/WidgetEndpoint.cs @@ -1,4 +1,4 @@ -W// Copyright (C) 2024 Vaughn Nugent +// Copyright (C) 2024 Vaughn Nugent // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as @@ -13,39 +13,37 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +using System; using System.Net; using System.Threading.Tasks; +using System.Text; +using VNLib.Utils.IO; +using VNLib.Utils.Memory; +using VNLib.Utils.Logging; +using VNLib.Utils.Extensions; +using VNLib.Net.Http; using VNLib.Plugins; using VNLib.Plugins.Essentials; using VNLib.Plugins.Essentials.Endpoints; using VNLib.Plugins.Extensions.Loading; using VNLib.Plugins.Essentials.Extensions; +using VNLib.Plugins.Extensions.Loading.Routing; using SimpleBookmark.Model; using SimpleBookmark.Model.Widget; namespace SimpleBookmark.Endpoints { + [EndpointPath("{{path}}")] + [EndpointLogName("widget-endpoint")] [ConfigurationName("widgets")] - internal sealed class WidgetEndpoint : UnprotectedWebEndpoint + internal sealed class WidgetEndpoint(PluginBase plugin, IConfigScope config) : UnprotectedWebEndpoint { - private readonly BookmarkStore bookmarks; - private readonly WidgetAuthManager authManager; - private readonly bool Enabled; - private readonly string? CorsAclHeaerDomains; - - public WidgetEndpoint(PluginBase plugin, IConfigScope config) - { - string path = config.GetRequiredProperty("path", static p => p.GetString()!); - InitPathAndLog(path, plugin.Log.CreateScope("widget-endpoint")); - - Enabled = config.GetValueOrDefault("enabled", static p => p.GetBoolean(), false); - CorsAclHeaerDomains = config.GetValueOrDefault("cors-urls", static p => p.GetString(), null); - - bookmarks = plugin.GetOrCreateSingleton(); - authManager = plugin.GetOrCreateSingleton(); - } + private readonly BookmarkStore bookmarks = plugin.GetOrCreateSingleton(); + private readonly WidgetAuthManager authManager = plugin.GetOrCreateSingleton(); + private readonly bool Enabled = config.GetValueOrDefault("enabled", false); + private readonly string? CorsAclHeaerDomains = config.GetValueOrDefault("cors-urls", null); protected override async ValueTask GetAsync(HttpEntity entity) { @@ -54,13 +52,13 @@ namespace SimpleBookmark.Endpoints return VfReturnType.NotFound; } - /* if (!await authManager.IsTokenValidAsync(entity)) - { - return VirtualClose(entity, HttpStatusCode.Unauthorized); - }*/ + /* if (!await authManager.IsTokenValidAsync(entity)) + { + return VirtualClose(entity, HttpStatusCode.Unauthorized); + }*/ //Widgets might be loaded in an iframe, so we need to allow cross-site requests - if(CorsAclHeaerDomains is not null && entity.Server.IsCrossSite()) + if (CorsAclHeaerDomains is not null && entity.Server.IsCrossSite()) { entity.Server.Headers.Append("Access-Control-Allow-Origin", CorsAclHeaerDomains); } @@ -79,7 +77,84 @@ namespace SimpleBookmark.Endpoints entity.EventCancellation ); - return VirtualCloseJson(entity, boomarks, HttpStatusCode.OK); + //Output memory buffer + VnMemoryStream output = new(32 * 1024, false); + + try + { + string baseUrl = entity.Server.RequestUri.GetLeftPart(UriPartial.Authority); + + CompileGlanceTemplate(baseUrl, output, boomarks); + + //Assign glance template + entity.Server.Headers["Widget-Title"] = "Simple-Bookmark Widget"; + entity.Server.Headers["Widget-Content-Type"] = "html"; + + return VirtualClose(entity, HttpStatusCode.OK, ContentType.Html, output); + } + catch(Exception ex) + { + output.Dispose(); + + Log.Error(ex, "Failed to complie glance template"); + return VirtualClose(entity, HttpStatusCode.InternalServerError); + } + } + + private static void CompileGlanceTemplate(string hostUrl, VnMemoryStream vms, BookmarkEntry[] bookmarks) + { + using IMemoryHandle buffer = MemoryUtil.SafeAlloc(32 * 1024, false); + + ForwardOnlyWriter writer = new(buffer.Span); + + writer.Append(""); + writer.Append(""); + writer.Append(""); + writer.Append("Simple-Bookmark Widget"); + writer.Append(""); + writer.Append(""); + + writer.Append("

"); + writer.Append("Favorites:"); + writer.Append("

"); + writer.Append("
"); + + //Start bookmarks list + writer.Append(""); + + //Close the document + writer.Append(""); + writer.Append(""); + + int byteCount = Encoding.UTF8.GetByteCount(writer.AsSpan()); + + using (UnsafeMemoryHandle utf8Buffer = MemoryUtil.UnsafeAllocNearestPage(byteCount, true)) + { + //Encode utf8 bytes + Encoding.UTF8.GetBytes(writer.AsSpan(), utf8Buffer.Span); + + vms.Write(utf8Buffer.AsSpan(0, byteCount)); + } + + vms.Seek(0, System.IO.SeekOrigin.Begin); } } } -- cgit