Skip to content

Commit

Permalink
Replace middleware with simple endpoints (#17373)
Browse files Browse the repository at this point in the history
  • Loading branch information
jbytes1027 authored Feb 7, 2025
1 parent c9ed4be commit f610347
Show file tree
Hide file tree
Showing 15 changed files with 303 additions and 155 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
using System.IO.Hashing;
using System.Text;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Caching.Memory;
using OrchardCore.Facebook.Settings;
using OrchardCore.Settings;

#nullable enable

namespace OrchardCore.Facebook.Endpoints;

public static class GetSdkEndpoints
{
public static IEndpointRouteBuilder AddSdkEndpoints(this IEndpointRouteBuilder builder)
{
builder.MapGet("/OrchardCore.Facebook/sdk/init.js", GetInitScriptEndpoint.HandleRequestAsync)
.AllowAnonymous()
.DisableAntiforgery();

builder.MapGet("/OrchardCore.Facebook/sdk/sdk.{culture:length(2,6)}.js", GetFetchScriptEndpoint.HandleRequestAsync)
.AllowAnonymous()
.DisableAntiforgery();

return builder;
}

public static class GetInitScriptEndpoint
{
// Update this version when the script changes to invalidate client caches
private static readonly int ScriptVersion = 1;

private static readonly string CacheKey = "Facebook.GetInitScriptEndpoint";

public static ulong HashCacheBustingValues(FacebookSettings settings)
{
var hash = new XxHash3(ScriptVersion);
hash.Append(Encoding.UTF8.GetBytes(settings.AppId ?? ""));
hash.Append(Encoding.UTF8.GetBytes(settings.Version ?? ""));
hash.Append(Encoding.UTF8.GetBytes(settings.FBInitParams ?? ""));
return hash.GetCurrentHashAsUInt64();
}

public static async Task<IResult> HandleRequestAsync(HttpContext context, ISiteService siteService, IMemoryCache cache)
{
byte[] scriptBytes;
var settings = await siteService.GetSettingsAsync<FacebookSettings>();
// Regenerate hash: Don't trust url hash because it could cause cache issues
var expectedHash = HashCacheBustingValues(settings);

var cachedScript = cache.Get(CacheKey) as KeyValuePair<ulong, byte[]>?;
if (cachedScript == null || cachedScript.Value.Key != expectedHash)
{
// Note: Update ScriptVersion constant when the script changes
// Note: All injected values except those in url must be used in HashCacheBustingValues
scriptBytes = Encoding.UTF8.GetBytes($@"
window.fbAsyncInit = function() {{
FB.init({{
appId:'{settings.AppId}',
version:'{settings.Version}',
{settings.FBInitParams}
}});
}};");

cache.Set(CacheKey,
new KeyValuePair<ulong, byte[]> (expectedHash, scriptBytes),
new MemoryCacheEntryOptions().SetSlidingExpiration(TimeSpan.FromHours(1)));
}
else
{
scriptBytes = cachedScript.Value.Value;
}

// Set the cache timeout to the maximum allowed length of one year
// max-age is needed because immutable is not widely supported
context.Response.Headers.CacheControl = "public, max-age=31536000, immutable";

return Results.Bytes(scriptBytes, "application/javascript");
}
}

public static class GetFetchScriptEndpoint
{
// Update this version when the script changes to invalidate client caches
private static readonly int ScriptVersion = 1;

private static readonly string CacheKey = "Facebook.GetFetchScriptEndpoint";

// Scraped from facebook.com
public static readonly string[] ValidFacebookCultures = { "en-US", "es-LA", "pt-BR", "fr-FR", "de-DE", "so-SO", "af-ZA", "az-AZ", "id-ID", "ms-MY", "jv-ID", "cx-PH", "bs-BA", "br-FR", "ca-ES", "cs-CZ", "co-FR", "cy-GB", "da-DK", "de-DE", "et-EE", "en-GB", "en-US", "es-LA", "es-ES", "eo-EO", "eu-ES", "tl-PH", "fo-FO", "fr-CA", "fr-FR", "fy-NL", "ff-NG", "fn-IT", "ga-IE", "gl-ES", "gn-PY", "ha-NG", "hr-HR", "rw-RW", "iu-CA", "ik-US", "is-IS", "it-IT", "sw-KE", "ht-HT", "ku-TR", "lv-LV", "lt-LT", "hu-HU", "mg-MG", "mt-MT", "nl-NL", "nb-NO", "nn-NO", "uz-UZ", "pl-PL", "pt-BR", "pt-PT", "ro-RO", "sc-IT", "sn-ZW", "sq-AL", "sz-PL", "sk-SK", "sl-SI", "fi-FI", "sv-SE", "vi-VN", "tr-TR", "nl-BE", "zz-TR", "el-GR", "be-BY", "bg-BG", "ky-KG", "kk-KZ", "mk-MK", "mn-MN", "ru-RU", "sr-RS", "tt-RU", "tg-TJ", "uk-UA", "ka-GE", "hy-AM", "he-IL", "ur-PK", "ar-AR", "ps-AF", "fa-IR", "cb-IQ", "sy-SY", "tz-MA", "am-ET", "ne-NP", "mr-IN", "hi-IN", "as-IN", "bn-IN", "pa-IN", "gu-IN", "or-IN", "ta-IN", "te-IN", "kn-IN", "ml-IN", "si-LK", "th-TH", "lo-LA", "my-MM", "km-KH", "ko-KR", "zh-TW", "zh-CN", "zh-HK", "ja-JP", "ja-KS" };

public static ulong HashCacheBustingValues(FacebookSettings settings)
{
var hash = new XxHash3(ScriptVersion);
hash.Append(Encoding.UTF8.GetBytes(settings.SdkJs ?? ""));
return hash.GetCurrentHashAsUInt64();
}

public static async Task<IResult> HandleRequestAsync(string culture, HttpContext context, IMemoryCache cache, UrlEncoder urlEncoder, ISiteService siteService)
{
byte[] scriptBytes;
var settings = await siteService.GetSettingsAsync<FacebookSettings>();
// Regenerate hash: Don't trust url hash because it could cause cache issues
var expectedHash = HashCacheBustingValues(settings);

var cachedScript = cache.Get(CacheKey) as KeyValuePair<ulong, byte[]>?;
if (cachedScript == null || cachedScript.Value.Key != expectedHash)
{
var encodedCulture = urlEncoder.Encode(culture.Replace('-', '_'));

// Note: If a culture is not found, facebook will use en_US
// Note: Update ScriptVersion constant when the script changes
// Note: All injected values except those in url must be used in HashCacheBustingValues
scriptBytes = Encoding.UTF8.GetBytes($@"(function(d){{
var js, id = 'facebook-jssdk'; if (d.getElementById(id)) {{ return; }}
js = d.createElement('script'); js.id = id; js.async = true;
js.src = ""https://connect.facebook.net/{encodedCulture}/{settings.SdkJs}"";
d.getElementsByTagName('head')[0].appendChild(js);
}} (document));");

cache.Set(CacheKey,
new KeyValuePair<ulong, byte[]> (expectedHash, scriptBytes),
new MemoryCacheEntryOptions().SetSlidingExpiration(TimeSpan.FromHours(1)));
}
else
{
scriptBytes = cachedScript.Value.Value;
}

// Set the cache timeout to the maximum allowed length of one year
// max-age is needed because immutable is not widely supported
context.Response.Headers.CacheControl = "public, max-age=31536000, immutable";

return Results.Bytes(scriptBytes, "application/javascript");
}
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Globalization;
using Microsoft.AspNetCore.Mvc.Filters;
using OrchardCore.Admin;
using OrchardCore.Facebook.Settings;
Expand Down Expand Up @@ -31,6 +32,7 @@ public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultE
if (settings.FBInit)
{
var setting = _resourceManager.RegisterResource("script", "fb");
setting.Culture = CultureInfo.CurrentUICulture.Name;
setting.AtLocation(ResourceLocation.Foot);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.Facebook" />
<PackageReference Include="System.IO.Hashing" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -1,25 +1,36 @@
using Microsoft.Extensions.Options;
using OrchardCore.Facebook.Endpoints;
using OrchardCore.Facebook.Settings;
using OrchardCore.ResourceManagement;
using OrchardCore.Settings;

namespace OrchardCore.Facebook;

public sealed class ResourceManagementOptionsConfiguration : IConfigureOptions<ResourceManagementOptions>
{
private static readonly ResourceManifest _manifest;
private readonly ISiteService _siteService;

static ResourceManagementOptionsConfiguration()
public ResourceManagementOptionsConfiguration(ISiteService siteService)
{
_manifest = new ResourceManifest();
_siteService = siteService;
}

public async void Configure(ResourceManagementOptions options)
{
var settings = await _siteService.GetSettingsAsync<FacebookSettings>();

_manifest
var manifest = new ResourceManifest();

manifest
.DefineScript("fb")
.SetDependencies("fbsdk")
.SetUrl("~/OrchardCore.Facebook/sdk/fb.js");
.SetUrl($"~/OrchardCore.Facebook/sdk/init.js?v={GetSdkEndpoints.GetInitScriptEndpoint.HashCacheBustingValues(settings)}");

_manifest
manifest
.DefineScript("fbsdk")
.SetUrl("~/OrchardCore.Facebook/sdk/fbsdk.js");
}
.SetCultures(GetSdkEndpoints.GetFetchScriptEndpoint.ValidFacebookCultures)
.SetUrl($"~/OrchardCore.Facebook/sdk/sdk.js?v={GetSdkEndpoints.GetInitScriptEndpoint.HashCacheBustingValues(settings)}");

public void Configure(ResourceManagementOptions options) => options.ResourceManifests.Add(_manifest);
options.ResourceManifests.Add(manifest);
}
}
64 changes: 0 additions & 64 deletions src/OrchardCore.Modules/OrchardCore.Facebook/ScriptsMiddleware.cs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ public class FacebookSettings
public string AppSecret { get; set; }
public bool FBInit { get; set; }

public string FBInitParams { get; set; } = @"status:true,
xfbml:true,
autoLogAppEvents:true";
public string FBInitParams { get; set; } = """
status: true,
xfbml: true,
autoLogAppEvents: true
""";

public string SdkJs { get; set; } = "sdk.js";
public string Version { get; set; } = "v3.2";
Expand Down
3 changes: 2 additions & 1 deletion src/OrchardCore.Modules/OrchardCore.Facebook/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using OrchardCore.DisplayManagement.Handlers;
using OrchardCore.Facebook.Endpoints;
using OrchardCore.Facebook.Drivers;
using OrchardCore.Facebook.Filters;
using OrchardCore.Facebook.Recipes;
Expand All @@ -20,7 +21,7 @@ public sealed class Startup : StartupBase
{
public override void Configure(IApplicationBuilder builder, IEndpointRouteBuilder routes, IServiceProvider serviceProvider)
{
builder.UseMiddleware<ScriptsMiddleware>();
routes.AddSdkEndpoints();
}

public override void ConfigureServices(IServiceCollection services)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
@using System.Globalization
@using OrchardCore.Entities
@using OrchardCore.Settings
@using OrchardCore.Facebook.Settings
@model OrchardCore.Facebook.Widgets.ViewModels.FacebookPluginPartViewModel
@inject ISiteService SiteService
@{
var fbInit = (await SiteService.GetSettingsAsync<FacebookSettings>()).FBInit;
var culture = CultureInfo.CurrentUICulture.Name;
}

@Html.Raw(Model.Html)

@if (!fbInit)
{
<script asp-name="fb" at="Foot"></script>
<script asp-name="fb" culture="@culture" at="Foot"></script>
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
@using System.Globalization
@using OrchardCore.Entities
@using OrchardCore.Settings
@using OrchardCore.Facebook.Settings
@model OrchardCore.Facebook.Widgets.ViewModels.FacebookPluginPartViewModel
@inject ISiteService SiteService
@{
var fbInit = (await SiteService.GetSettingsAsync<FacebookSettings>()).FBInit;
var culture = CultureInfo.CurrentUICulture.Name;
}

@Html.Raw(Model.Html)

@if (!fbInit)
{
<script asp-name="fb" at="Foot"></script>
<script asp-name="fb" culture="@culture" at="Foot"></script>
}
Loading

0 comments on commit f610347

Please sign in to comment.