DNN · Embebido del widget
El widget se carga desde el CDN como un único <script> con data-token apuntando al Widget Token (24 h) emitido por Neuroon. La pieza específica de DNN es:
- Emisión del token server-to-server desde DNN, con cache en memoria.
- Render del
<script>en un.ascx(Skin o SkinObject) imprimiendo el token desde code-behind. - SRI fija para asegurar la integridad del binario del widget.
El token NO lo firma el cliente
El Widget Token es opaco y emitido por el backend Neuroon. Tu integración no firma nada; solicita el token con tu Shop API Key y lo inyecta en
data-token. Ver Authentication · Widget Token.
Endpoint usado:
/api/shops/{id}/widget-tokenCache server-side (24 h con margen)
DNN no debería pegarse al endpoint /widget-token en cada request. El patrón estándar:
public static class WidgetTokenProvider
{
private static readonly object Lock = new();
private static string _token;
private static DateTime _expires;
public static string Get()
{
lock (Lock)
{
if (_token != null && DateTime.UtcNow < _expires.AddMinutes(-5))
return _token;
var fresh = new NeuroonClient().IssueWidgetTokenAsync().GetAwaiter().GetResult();
_token = fresh;
_expires = DateTime.UtcNow.AddHours(24);
return _token;
}
}
}
Notas:
- Margen de 5 minutos antes de expirar para evitar
401en peticiones de borde. - En entornos multi-AppPool / web farm, sustituye el
staticpor una cache distribuida (Redis,OutputCache). - En .NET 8, prefiereSemaphoreSlimoIMemoryCacheconLazy<Task<string>>.
Render en .ascx
Skin / SkinObject mínimo. Coloca un placeholder donde quieras la barra de búsqueda:
<%@ Control Language="C#" AutoEventWireup="true" %>
<%@ Register TagPrefix="dnn" Namespace="DotNetNuke.UI.Skins.Controls" Assembly="DotNetNuke" %>
<asp:PlaceHolder runat="server">
<div id="neuroon-search"></div>
<script
src="https://cdn.neuroon.ai/widget@0.9.10/widget.js"
integrity="sha384-JTaG/IN0Jj/ImfUj2x5QVMG4HkbFHzui7fTpLtwl1hsP+kY9W8OODeSJRFWN1ZP5"
data-token='<%= WidgetTokenProvider.Get() %>'
data-container="#neuroon-search"
data-theme="auto"
data-locale='<%= System.Threading.Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName %>'
data-api-url='<%= NeuroonSettings.ApiUrl %>'
crossorigin="anonymous"
async></script>
</asp:PlaceHolder>
El SRI hash y la versión del widget proceden del manifest oficial:
https://cdn.neuroon.ai/widget@VERSION/sri-manifest.json. La versión0.9.10y su hash provienen del plugin WordPress .
CSP (Content Security Policy)
Si tu Skin aplica CSP estricta, añade los hosts mínimos:
Content-Security-Policy:
script-src 'self' https://cdn.neuroon.ai;
connect-src 'self' https://api.neuroon.ai https://dev-api.neuroon.ai;
img-src 'self' data: https:;
style-src 'self' 'unsafe-inline';
Para Production-only, omite dev-api.neuroon.ai. El widget no requiere unsafe-eval.
Locale
data-locale acepta cualquier código ISO 639-1 (es, en, de, etc.). En DNN, lo natural es:
data-locale='<%= System.Threading.Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName %>'
Si tu site es multi-portal, usa PortalSettings.Current.PortalAlias.CultureCode. Lee también Widget · i18n para el comportamiento de fallback en el cliente.
Verificar el embebido
- Abre la página pública con DevTools.
- Network → Filter
widget: deberías ver una petición ahttps://cdn.neuroon.ai/widget@0.9.10/widget.jscon status200y headerX-Content-Type-Options: nosniff. - Console: ningún
Failed to find a valid digest in the 'integrity' attribute. Si aparece, el SRI o la versión no coinciden. - Application → Cookies: el widget no debería poner cookies de tracking en este dominio.
Embebido condicional
Para renderizar el widget solo en ciertas páginas / portales:
protected void Page_PreRender(object sender, EventArgs e)
{
var portal = PortalSettings.Current;
if (portal != null && portal.HomeTabId == this.TabId)
{
widgetPlaceholder.Visible = true;
}
}
Próximos pasos
- Cart bridge — sincroniza el carrito con el widget.
- Ejemplos · Razor snippets — más patrones de embebido.
- Authentication · Widget Token — TTL y rotación.