Back to "StyloBot-utsläppsserien"

This is a viewer only at the moment see the article on how this works.

To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk

This is a preview from the server running through my markdig pipeline

Bot Detection Node.js Security StyloBot TypeScript

StyloBot-utsläppsserien

Monday, 01 June 2026

Bot-detektorn borde inte stanna vid att tillåta/block. Denna artikel visar hur StyloBots klassificeringsresultat blir till applikationslogik i TypeScript : server middleware för Express och Fastify MSC4 template helpers för Handlebars M SK5 Nunjucks , och EJS MST7 och webbkomponenter för webbfarkoster| , så att din gränssnitt kan forma upplevelsen istället för att knuffa på friktionen efter själva faktat.

StyloBot

Den @stylobot/core och @stylobot/node npm-paket kommer snart att publiceras. De här fragmenten beskriver ytan som de ska utsätta

StyloBot-utsläppsserien

  1. Behavior, Inte Identitet: varför StyloBot modellerar klienter beteendemässigt
  2. Behavior-Är medveten ASP.NET UI: servern
  3. Att hitta och lösa gränslös tillväxt i långtids-, -- och / eller körande- -.-nätverk: den tillförlitlighetsdisciplin som gör motorn tråkig i produktionen
  4. Behavior-Akvis typscript-UI: den här artikeln
  5. Sidecar-arkitekturen: hur detektoren kopplas till icke--.NET-skivor
  6. Att lära sig bli snabbare: adaptiv inlärningssystem, ,, fyra, M SK2, övningsminnet, ,, och bedömningsキャッシュen
  7. Att testa det som inte stannar kvar: kontrolldisciplinen : en BDF-fielddriver regression M SK2 belastning , och kalibrering
  8. StyloExtract - en lokal lärande HTML omvandlare till Markdown: HTML, →, Markdown-lagret som paras med detektorn, ,, walkersbug lucidVIEW som fångade, M SK3, och den hundföda-loopan som gjorde det ärligt

StyloBots detektormotor är skriven i ASP, ., NET Core och :, ett högpresterande ramverk med utmärkta asyncka primitiver. ,, sub, -, millisekunderlig värme och latens på banan - och tjugo år av produktionskapande bakgrund. Att lära sig bli snabbare: när fingeravtrycket har setts tillräckligt noggrant, , hoppar bedömande kasken över det fulla spårningsröret och svarar i mikrosekunder

Inleiding

De flesta botdetektorer som finns i TypeScripts ekosystem ger dig en av två saker - : - ett fingeravtryck från en webblader som talar om klient ser automatiserad ut, eller en regex över Benutzern -En agentsträng som fångar bara de robotar som annonserar sig själva

Inget av dem är användbart på applikationslayern.

botd drivs i webbläsaren och har ingen bild av begäranschefer, fingeravtryck från TLS , inspelningsbeteende, eller IP-värdeM SK3 En huvudlös Chromium och en Googlebot ser likadana ut som denis-bot, isbot) fånga bara det som väljer att bli fångad, . nätverk, - lagerprodukter, | ( | Cloudflare Bot Management, , | DataDome, M SK5 | Block på gränsen men inte personaliserad på applikationslayret, ♫ . | Du får reda på att en begäran blockerades, ♪ , | aldrig att en begäran som nådde din app hade en ♪0.87 | bot sannolikhet och en medelriskgrupp, \ , | så din kontosida kunde ha visat en friktionstillgång, och din analys skulle kunna ha uteslutit den, ♪

Vad StyloBot ger din TypeScript-app är en bedömelse: ett tryckt resultat med en kontinuerlig bot sannolikhetsscore , en klassificering (AiBot, Scraper, GoodBot, MaliciousBot,, ...),, en riskgrupp, ,, en tillämpt åtgärd, M SK3, och ett hotsvärde. ., vad du gör med det Urteilet är upp till dig.

Den här artikeln går genom ett exemplar av butiksfront och visar hur det Urteilet blir till applikationsbeteende på varje lager.

Bedömelsen

Innan något annat kommer det här:

// @stylobot/core
interface Verdict {
  isBot: boolean;
  botProbability: number;        // 0.0–1.0 - not binary, a continuous score
  confidence: number;            // certainty in that score
  botType: BotType | null;       // 'AiBot' | 'Scraper' | 'GoodBot' | 'MaliciousBot' | ...
  botName: string | null;        // 'GPTBot', 'Googlebot', 'curl/8.9.1', ...
  riskBand: RiskBand;            // 'VeryLow' | 'Low' | 'Elevated' | 'Medium' | 'High' | 'VeryHigh'
  recommendedAction: RecommendedAction;  // 'Allow' | 'Throttle' | 'Challenge' | 'Block'
  threatScore: number;           // elevated when CVE probes or credential stuffing detected
  threatBand: ThreatBand;        // 'None' | 'Low' | 'Elevated' | 'High' | 'Critical'
}

En kassa slutpunkt kan kväva Medium risk istället för att blockera det. En API kan AiBot trafik från hastigheten-begränsa kvoten utan att räkna det mot verkliga användaren. Ett loggande Formular kan lägga till bevis Elevated risk utan att röra vid upplevelsen för Low risk-biträdare. Inget av detta är möjligt om det detekterade resultatet är en booleer på nätverkslayret

Riskband i ordning: VeryLow, Low, Elevated, Medium, High, VeryHigh, Verified.

Installation

npm install @stylobot/core @stylobot/node

# only needed for grpc mode
npm install @grpc/grpc-js @grpc/proto-loader

@stylobot/core har noll drivtids beroenden. Den innehåller typdefinitionerna , den REST-clienten , den header-parsern M SK3 och ( optionellt Mska5 gRPC-clientet Msaka6 Den fungerar i Node M Ska7 Deno M ska8 och Bun utan modifiering

@stylobot/node erbjuder Express middleware, en Fastify- plugin , template helper utilities, och den bedömande injicernM SK3 det beror på @stylobot/core och inget annat vid starttid.

Serverytan

Hur dömelsen når din mellanverk

Mittvaran har tre olika trybar . Alla tre producerar samma req.stylobot form. Vilken som du använder beror på var i din infrastruktur detektor körs

graph LR
    classDef input fill:none,stroke:#3b82f6,stroke-width:2px
    classDef good fill:none,stroke:#22c55e,stroke-width:2px
    classDef async fill:none,stroke:#a855f7,stroke-width:2px

    subgraph H ["headers mode - zero added latency"]
        GW[Gateway / Caddy / YARP]:::input -->|"X-StyloBot-* headers"| A[Express / Fastify]:::good
    end

    subgraph G ["grpc mode - HTTP/2, no JSON overhead"]
        B[Express / Fastify]:::good -->|"gRPC Detect RPC"| SC[StyloBot Sidecar :5090]:::async
    end

    subgraph R ["api mode - REST, full signal set"]
        C[Express / Fastify]:::good -->|"POST /api/v1/detect"| SC2[StyloBot Sidecar :5091]:::async
    end

headers ModusDetektorn kördes uppströms vid portaalet.;.Mittvaran läser injicerade opskrifte utan nätverkssändning... Detta är produktionsmönstret när en Caddy eller YARP portaal sitter framför.

grpc Modus: mellanvaran ropar på bibilen direkt via HTTP /2. minst latens @grpc/grpc-js. Detailen om gRPC-gränssnittet och Sidecar-arkitekturen artikel.

api Modus: samtal POST /api/v1/detect över HTTP/1.1. Även återvänder reasons ( signals ( den fulla svarta brädans tillståndet ) som andra former utelämnar

Mittvaran misslyckas alltid att öppna. Om bibilen inte är tillgänglig eller går ut req.stylobot ställs till ett tillåtande tomt slutsats och begäran fortsätter.

Att koppla upp det

Express:

import express from 'express';
import { styloBotMiddleware } from '@stylobot/node';

const app = express();
app.use(styloBotMiddleware({ mode: 'headers' }));
// or:
app.use(styloBotMiddleware({ mode: 'grpc', endpoint: 'localhost:5090', timeout: 100 }));

Fastify:

import Fastify from 'fastify';
import { styloBotPlugin } from '@stylobot/node';

const fastify = Fastify();
await fastify.register(styloBotPlugin, { mode: 'headers' });

Lägg till en typdeklaration om du vill ha skrivna tillgångar på Fastify requests:

// types.d.ts
import type { StyloBotResult } from '@stylobot/node';
declare module 'fastify' {
  interface FastifyRequest { stylobot: StyloBotResult; }
}

Storefronten

Samma exemplar i butiken från ASP.NET artikel, produktsida , utgångslistan, inloggningM SK3 newslettern M SK4 men i TypeScript . Det detekterade resultatet är samma МSK6 vad som ändrar är hur ytan exponerar det

Page 1: Artikelsidan

Kursskrapper börjar här. De vill ha namn på produkterna , priser M SK2 och lagernivåer . Ni vill ha katalogen indexerad av sökmotorer och användbar för människor utan att ge kommersiella signaler till systematiskt skörd

Scenario: en priskrapper träffar /products/:id. Den ser inga rabattkoder , ingen tillsättning - till M SK3 kartknappen MSC4 och inget köpsignal värd att agera på

Routenoperatören:

app.get('/products/:id', (req, res) => {
  const { verdict } = req.stylobot;
  const product = getProduct(req.params.id);

  res.render('product', {
    product,
    sbVerdict: verdict,
    showDiscount:   verdict.riskBand === 'VeryLow' || verdict.riskBand === 'Low',
    showBuyButton:  !verdict.isBot,
    showCartWarning: verdict.recommendedAction === 'Challenge',
    isSearchBot:    verdict.botType === 'SearchEngine' || verdict.botType === 'VerifiedBot',
  });
});

Vorlagen (Handlebars):

{{! verified crawlers get structured metadata instead of commercial UI }}
{{#if isSearchBot}}
  <meta name="description" content="{{product.name}} -{{product.category}}." />
  <p>{{product.description}}</p>
{{else}}

  {{! discount only for low-risk human visitors }}
  {{#if showDiscount}}
    <div class="alert alert-success">
      Member price: use code <strong>LOYAL10</strong> for 10% off.
    </div>
  {{/if}}

  {{! add-to-cart only for non-bot traffic }}
  {{#if showBuyButton}}
    <form method="post" action="/cart/add">
      <input type="hidden" name="productId" value="{{product.id}}" />
      {{#if showCartWarning}}
        <p class="text-warning">Additional verification may be required at checkout.</p>
      {{/if}}
      <button type="submit" class="btn btn-primary">Add to Cart</button>
    </form>
  {{else}}
    <p class="text-muted">Purchase available to human visitors.</p>
  {{/if}}

{{/if}}

Tre mönster som används här. Crawler-differentiering : SearchEngine och VerifiedBot få strukturerade metadata utan kommersiella signaler , vilket är vad de faktiskt behöver. Försämpningstarkulationen : en lojalitetskod som bara visar sig vara lågt M SK3 risktrafiken är mindre benägen att hamna på ett kupongforum Challenge är en varning, inte en block

sida 2: Kasse

Automatisering av bedrägerier, korttestare , kupong brute- kraftverkareM SK3 och skriptade retryr förbereder sig alla vid affärskontrollenMSC4 målet är att försvara djupt utan att behöva någon CAPTCHA

Scenario: en kupong - testbot träffar /checkout. Den ser ett dödt - slut meddelande , skickar ett honungsbehåller, - fyllt form, M SK4 och får en tyst bekräftelse utan att uppmuntra till att pröva igen

flowchart TD
    classDef input fill:none,stroke:#3b82f6,stroke-width:2px
    classDef good fill:none,stroke:#22c55e,stroke-width:2px
    classDef problem fill:none,stroke:#ef4444,stroke-width:2px

    A([Visitor reaches /checkout]):::input --> B{bot?}
    B -->|yes| C[Dead-end message - no form]:::problem
    B -->|no| D{risk band}
    D -->|Low| E[Express checkout CTA]:::good
    D -->|Elevated| F[Standard checkout CTA]:::good
    D -->|High+| G[Phone number - no online path]:::problem
    E & F --> H[POST /checkout/order]
    H --> I{honeypot triggered?}
    I -->|yes| J[Silent fake confirmation]:::problem
    I -->|no| K[Process real order]:::good

Routen:

app.get('/checkout', (req, res) => {
  const { verdict } = req.stylobot;
  res.render('checkout', {
    sbVerdict: verdict,
    showForm:     !verdict.isBot,
    showExpress:  verdict.riskBand === 'VeryLow' || verdict.riskBand === 'Low',
    showStandard: ['VeryLow','Low','Elevated'].includes(verdict.riskBand),
    showPhone:    ['High','VeryHigh'].includes(verdict.riskBand),
  });
});

app.post('/checkout/order', (req, res) => {
  // honeypot fields are invisible to humans; bots fill everything
  if (req.body.hp_name || req.body.hp_email) {
    return res.redirect('/checkout/confirmed');  // silent fake - no retry incentive
  }
  return processRealOrder(req, res);
});

Vorlagen:

{{#if showForm}}
  <form method="post" action="/checkout/order">

    {{! invisible honeypot trap - humans leave blank, bots fill everything }}
    <div style="position:absolute;left:-9999px;opacity:0" aria-hidden="true">
      <input type="text" name="hp_name" tabindex="-1" autocomplete="off" />
      <input type="email" name="hp_email" tabindex="-1" autocomplete="off" />
    </div>

    {{#if showExpress}}
      <button type="submit" name="express" value="true" class="btn btn-success btn-lg">
        Express Checkout
      </button>
    {{/if}}

    {{#if showStandard}}
      <button type="submit" class="btn btn-primary">Proceed to Payment</button>
    {{/if}}

    {{#if showPhone}}
      <p>Please call us to complete your order: <strong>0800 123 456</strong></p>
    {{/if}}

  </form>
{{else}}
  <p class="text-muted">Checkout is available to human visitors only.</p>
{{/if}}

Honigkaka slängning är den viktigaste tekniken här...Fehler återkoppling lär attacker att itera.;. en tyst falsk bekräftelse slösar bort sin tid. ,. Bekräftelsesidan ser identisk ut, oavsett om beställningen var verklig eller slängd. <div> wrapper in i ett gemensamt Handlebars del ({{> honeypot}}) istället för att upprepa inline-stilblocket på varje skyddad form

Page 3: logga in

Att logga in är krävbart- när det handlar om mat, . Världen är annorlunda från att lämna pengarna på affären.: ett falskt positivt till affärerna kan förlora en affär.; ett misslyckat krävande.

Scenariot : en credential- med hjälp av skripta träffar /login. Den ser en avskräckande meddelande , täcker honungskålen /login/denied innan någon autentisering körs.

app.get('/login', (req, res) => {
  const { verdict } = req.stylobot;
  res.render('login', {
    sbVerdict: verdict,
    showWarning: verdict.isBot,
    showRisk:    ['High','VeryHigh'].includes(verdict.riskBand),
  });
});

app.post('/login', async (req, res) => {
  if (req.body.hp_user || req.body.hp_pass) {
    return res.redirect('/login/denied');
  }
  if (req.stylobot.verdict.isBot) {
    return res.redirect('/login/denied');
  }
  return authenticate(req, res);
});
{{#if showRisk}}
  <div class="alert alert-danger">
    High-risk signals detected. Login attempts are logged.
  </div>
{{/if}}

{{#if showWarning}}
  <div class="alert alert-warning">
    Automated login attempts are detected and blocked.
  </div>
{{/if}}

<form method="post" action="/login">
  <div style="position:absolute;left:-9999px;opacity:0" aria-hidden="true">
    <input type="text" name="hp_user" tabindex="-1" autocomplete="off" />
    <input type="password" name="hp_pass" tabindex="-1" autocomplete="off" />
  </div>
  <div class="form-group">
    <label for="email">Email</label>
    <input type="email" id="email" name="email" autocomplete="email" />
  </div>
  <div class="form-group">
    <label for="password">Password</label>
    <input type="password" id="password" name="password" autocomplete="current-password" />
  </div>
  <button type="submit" class="btn btn-primary">Sign In</button>
</form>

Boten ser att det är avskräckande innan den lägger upp den.. Om den gör det i alla fall, så fångar honungsburken den. verdict.isBot är den slutgiltiga servern.

Page 4: Newsletter

AI-kralare är ett annat problem.., de försöker inte brutera.-, de tvingar in konton eller gröda pris. ;, de smäller in innehåll för modellutbildningar, ., de är ofta ärliga om identitet.M SK4, vilket betyder att rätt lösning är kommersiellt snarare än motsägelsefullt.

Scenario: GPTBot träffar /newsletter. Den ser en datalicensenskaps meddelande istället för en subskryptionspitch

app.get('/newsletter', (req, res) => {
  const { verdict } = req.stylobot;
  res.render('newsletter', {
    sbVerdict:     verdict,
    isHuman:       !verdict.isBot,
    isAiBot:       verdict.botType === 'AiBot',
    isOtherBot:    verdict.isBot && verdict.botType !== 'AiBot',
  });
});

app.post('/newsletter/subscribe', (req, res) => {
  if (req.body.hp_email2 || req.stylobot.verdict.isBot) {
    return res.redirect('/newsletter/thanks');  // silent discard
  }
  mailingList.subscribe(req.body.email);
  return res.redirect('/newsletter/thanks');
});
{{#if isHuman}}
  <p>Get exclusive deals delivered to your inbox. Unsubscribe any time.</p>
{{/if}}

{{#if isAiBot}}
  <div class="alert alert-info">
    This subscription endpoint is for human readers.
    For data licensing enquiries, <a href="/contact">contact us directly</a>.
  </div>
{{/if}}

{{#if isOtherBot}}
  <div class="alert alert-warning">
    Automated subscription attempts are discarded.
  </div>
{{/if}}

<form method="post" action="/newsletter/subscribe">
  <div style="position:absolute;left:-9999px;opacity:0" aria-hidden="true">
    <input type="email" name="hp_email2" tabindex="-1" autocomplete="off" />
  </div>
  <div class="form-group">
    <input type="email" name="email" placeholder="[email protected]" autocomplete="email" />
  </div>
  <button type="submit" class="btn btn-success">Subscribe</button>
</form>

AiBot är inte bara ett annat fientligt etikett.

Template hjälparbetare

Injictering av bedömningen i templater

sbVerdictInjector kör efter det upptäckande mellanprogramet och sätter bedömelsen på res.locals:

import { sbVerdictInjector } from '@stylobot/node';

app.use(styloBotMiddleware({ mode: 'headers' }));
app.use(sbVerdictInjector({ mode: 'gateway' }));
// or for direct sidecar calls (REST port, not the gRPC port):
app.use(sbVerdictInjector({ mode: 'sidecar', endpoint: 'http://localhost:5091' }));

res.locals.sbVerdict är tillgänglig i varje form res.locals.sbVerdictScript är en inline <script> tag som sätter window.__sb för webbläsarens komponenter.

Handlebar

Definiera RISK_ORDER en gång vid modulspektrum ( använder Nunjucks och EJS hjälparbetarna härunder samma karta ):

import type { Verdict, RiskBand } from '@stylobot/core';

const RISK_ORDER: Record<string, number> = {
  Unknown: 0, VeryLow: 1, Low: 2, Elevated: 3, Medium: 4, High: 5, VeryHigh: 6, Verified: 7,
};

// register on your Handlebars engine instance
hbs.engine({
  helpers: {
    // {{#sbGate sbVerdict "Low"}}...{{else}}...{{/sbGate}}
    sbGate(verdict: Verdict | null, maxRisk: string, options: any) {
      if (!verdict) return options.fn(this);
      return RISK_ORDER[verdict.riskBand] <= RISK_ORDER[maxRisk]
        ? options.fn(this)
        : options.inverse(this);
    },

    // {{#sbIsBot sbVerdict}}...{{else}}...{{/sbIsBot}}
    sbIsBot(verdict: Verdict | null, options: any) {
      return verdict?.isBot ? options.fn(this) : options.inverse(this);
    },

    // {{#sbBotType sbVerdict "AiBot"}}...{{/sbBotType}}
    sbBotType(verdict: Verdict | null, type: string, options: any) {
      return verdict?.botType === type ? options.fn(this) : options.inverse(this);
    },

    // {{#sbMinRisk sbVerdict "High"}}...{{/sbMinRisk}}
    sbMinRisk(verdict: Verdict | null, minRisk: string, options: any) {
      if (!verdict) return options.inverse(this);
      return RISK_ORDER[verdict.riskBand] >= RISK_ORDER[minRisk]
        ? options.fn(this)
        : options.inverse(this);
    },
  },
});

I maller:

{{#sbGate sbVerdict "Low"}}
  <a href="/checkout/express" class="btn btn-success">Express Checkout</a>
{{else}}
  <a href="/checkout" class="btn btn-secondary">Checkout</a>
{{/sbGate}}

{{#sbBotType sbVerdict "AiBot"}}
  <div class="alert alert-info">AI crawler detected. Data licensing info above.</div>
{{/sbBotType}}

{{#sbMinRisk sbVerdict "High"}}
  <div class="alert alert-danger">High-risk session. Some features restricted.</div>
{{/sbMinRisk}}

Nunjucks

import nunjucks from 'nunjucks';

// RISK_ORDER - same map defined in the Handlebars section above

class SbGateExtension {
  tags = ['sbgate'];
  parse(parser: any, nodes: any, lexer: any) {
    const tok = parser.nextToken();
    const args = parser.parseSignature(null, true);
    parser.advanceAfterBlockEnd(tok.value);
    const body = parser.parseUntilBlocks('else', 'endsbgate');
    let elseBody = null;
    if (parser.skipSymbol('else')) {
      parser.skip(lexer.TOKEN_BLOCK_END);
      elseBody = parser.parseUntilBlocks('endsbgate');
    }
    parser.advanceAfterBlockEnd();
    return new nodes.CallExtension(this, 'run', args, [body, elseBody]);
  }
  run(context: any, maxRisk: string, body: any, elseBody: any) {
    const v = context.ctx.sbVerdict;
    const fits = !v || RISK_ORDER[v.riskBand] <= RISK_ORDER[maxRisk];
    return fits ? body() : (elseBody?.() ?? '');
  }
}
env.addExtension('SbGateExtension', new SbGateExtension());
{% sbgate "Low" %}
  <a href="/checkout/express">Express checkout</a>
{% else %}
  <a href="/checkout">Checkout</a>
{% endsbgate %}

EJS

EJS har inga block-helfer, men lokalfunktioner täcker de flesta fall rent

// RISK_ORDER - same map defined in the Handlebars section above

app.use((req, res, next) => {
  const v = res.locals.sbVerdict ?? null;
  res.locals.sbBelowRisk = (max: string) =>
    !v || RISK_ORDER[v.riskBand] <= RISK_ORDER[max];
  res.locals.sbAboveRisk = (min: string) =>
    v && RISK_ORDER[v.riskBand] >= RISK_ORDER[min];
  res.locals.sbIsBot  = () => v?.isBot ?? false;
  res.locals.sbBotIs  = (type: string) => v?.botType === type;
  next();
});
<% if (sbBelowRisk('Low')) { %>
  <a href="/checkout/express" class="btn btn-success">Express Checkout</a>
<% } else { %>
  <a href="/checkout" class="btn btn-secondary">Checkout</a>
<% } %>

<% if (sbBotIs('AiBot')) { %>
  <div class="alert alert-info">AI crawler. Data licensing enquiries via /contact.</div>
<% } %>

Browsergränssnittet

Injicerar bedömningen i webbläsaren

Lägg till: {{{sbVerdictScript}}} till ditt layout-kopf. Det renderar som

<script>window.__sb = {"isBot":false,"botProbability":0.12,"riskBand":"Low","recommendedAction":"Allow",...}</script>

Sedan ladda elementpaketet:

<head>
  {{{sbVerdictScript}}}
  <script type="module" src="/js/sb-elements.js"></script>
</head>

<sb-gate>: visa eller gömma innehållet efter riskband

<!-- only shown when riskBand <= 'low' -->
<sb-gate max-risk="low">
  <a href="/checkout/express" class="btn btn-success">Express Checkout</a>
</sb-gate>

<!-- only shown when riskBand >= 'elevated' -->
<sb-gate min-risk="elevated">
  <div class="alert alert-warning">Unusual activity detected on your session.</div>
</sb-gate>

<sb-adapt>: välja den första matchande fallen

<sb-adapt>
  <sb-case max-risk="low">
    Standard checkout - enter your card details below.
  </sb-case>
  <sb-case max-risk="medium">
    Additional verification is required before we can process your order.
  </sb-case>
  <sb-case>
    Automated checkout access is not permitted.
  </sb-case>
</sb-adapt>

<sb-adapt> tar den första <sb-case> vars max-risk passar och gömmer resten. Den slutliga <sb-case> utan max-risk är avfångst <sb-gate> och <sb-adapt> lyssna på sb:verdict händelse och re-värdera om bedömningen ändras efter den första målningen

<sb-widget>: server - skickade flytande fragment

<sb-gate> gömmer innehållsclient- sida. Om du behöver en robot för att få en annan markup , inte gömd markup~ , så behöver du fragmentet som skall användas på servern med det detekterande sammanhanget som är tillgängligt i szablonen

<sb-widget> läser en vätska-template från en inline <template> element, byter alla widget-behov på sidan till en enda cirkel -tripM SK2 och ersätter sig själv med den återvända HTML:

<sb-widget data-sb-widget="checkout-cta">
  <template>
    {% if recommendedAction == 'Block' %}
      <p class="text-danger">Automated checkout is blocked.</p>
    {% elsif botType == 'AiBot' %}
      <p>AI crawlers cannot complete purchases. <a href="/contact">Data licensing enquiries</a>.</p>
    {% elsif isBot %}
      <p class="text-muted">Checkout is available to human visitors.</p>
    {% else %}
      <a href="/checkout/express" class="btn btn-success">Express Checkout</a>
    {% endif %}
  </template>
</sb-widget>

Vorlagens variabler: isBot, botProbability, confidence, botType, botName, riskBand, recommendedAction, threatScore, threatBand. Plus någon vars du passerar från servern-sidan när du ska ringa RenderWidget direkt.

Alla <sb-widget> element som kopplas ihop i samma mikrotask tick är sammanslagna till en fråga. En sida med fem widgetar gör en runda

sequenceDiagram
    participant B as Browser
    participant C as Coordinator
    participant S as /_stylobot/partials/render
    participant L as Fluid / Liquid

    B->>C: sb-widget#checkout-cta registers
    B->>C: sb-widget#promo-banner registers
    B->>C: sb-widget#nav-links registers
    Note over C: queueMicrotask - one batch
    C->>S: POST { widgets: { checkout-cta: "...", promo-banner: "...", nav-links: "..." } }
    S->>L: render each template with verdict context
    L-->>S: rendered HTML fragments
    S-->>C: <div data-sb-widget="checkout-cta">...</div> ...
    C-->>B: each sb-widget replaced in DOM

Summaristabellen

Komponent ♫ ♫ Ytan ♫ ♫ Vad gör den ♫
styloBotMiddleware Express Anhängningar req.stylobot via opskrifte / gRPC / REST
styloBotPlugin Fastifiera Samma , som en Fastifier plugin med decorateRequest
sbVerdictInjector Express Befolkningar res.locals.sbVerdict och sbVerdictScript
Handlebars hjälper Server-templates ♫ sbGate, sbIsBot, sbBotType, sbMinRisk block hjälpmedel
Nunjucks-extension Server-templates {% sbgate "Low" %}...{% endsbgate %}
EJS lokaler Server-templates sbBelowRisk(), sbAboveRisk(), sbIsBot(), sbBotIs()
<sb-gate> Browser Anzeiger M SK2 gömmer innehållet efter riskband sb:verdict
<sb-adapt> Browser Välj den första matchande fallen från en lista
<sb-widget> Browser Fetch server - Renderat flytande fragment МSK3 Batched per tick
StyloBotGrpcClient Nod / Bun / Deno M Direkt gRPC-client för icke-

Nästa i releaseserien: Sidecar-arkitekturen, hur detektormotoren kopplas till icke-

Om du kom hit från .NET sida Behavior-Är medveten ASP.NET UI täcker samma hyllningsmönster med taghelpare och handlingsfilter.;. Den tillförlitlighet som håller maskinens minne, -. Stabilt under långvarig trafik. Att hitta och lösa gränslös tillväxt i långtids-, -- och / eller körande- -.-nätverk. Livmotorn , brädan, , och kommersiella kontrollen stylobot.net.

logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.