# Серія релізу StyloBot: Подібність поведінки-Усвідомлення інтерфейсу TypScript

*Виокремлення Bot не має зупинятися на дозволяти/блокуM SK1 Цей пост показує, як результат класифікації StyloBot ' стає логікою застосування в шрифті TypeScript МSK3 серверному середовищі для Express і Fastify МСК4 шаблонних вказівників для ручних панелей М СК5 Nunjucks М סК6 та EJS М S К7 і веб-компонентах браузера М ็ С К8 так що ваше інтерфейс може формувати досвід замість того, щоб відштовхувати тремтіння після факту M С К9*

[<img src="/articleimages/stylobot-logo.svg" alt="StyloBot" width="120" />](https://www.stylobot.net)

> **І `@stylobot/core` і `@stylobot/node` пакети npm незабаром будуть опубліковані.** Ниже представлені фрагменти описують поверхню, яку вони відкриють.

> **Серія випуску StyloBot**
> 
> 1. [**Подібність, Не ідентичність**](/blog/stylobot-fingerprint): чому StyloBot моделює клієнтів поведінково
> 2. [**Подібність-Усвідомлення ASP.NET UI**](/blog/behaviour-aware-ux): сервер - віддалена поверхня для МSK2NET застосувань
> 3. [**Винайти та виправити безмежний ріст у довгостроковій-Running M SK1NET Services**](/blog/stylobot-release-reliability): - дисципліна надійності, яка робить двигун нудним в процесі виробництва
> 4. **Подібність-Усвідомлення інтерфейсу TypScript**: цей artykuł
> 5. [**Архітектура Sidecar**](/blog/sidecar-architecture): як реагуючий двигун підключається до не МSK1NET стеків
> 6. [**Навчитися швидше**](/blog/stylobot-release-learning): адаптивна система навчання , чотири МSK2 пам 'ять на рівні M SK3 і кэш вердикта
> 7. [**Випробовуємо те, що не зупиняє нас**](/blog/stylobot-release-nondeterministic-testing): контрольна дисципліна : один БДФ файл керує регресією МSK2 завантаженням МСК3 та калібрацією
> 8. [**StyloExtract - локальний інтерфейс навчання HTML для конвертера Markdown**](/blog/stylobot-release-styloextract): HTML → Марка-даун, який поєднується з детектором МSK2 walker bug lucidVIEW caught МSK3 and the dogfood loop that made it honest

Мотор розпізнавання StyloBot , написаний в ASP, ., NET Core і :, має високий "-" показник, з чудовими асинковими примітивами. МSK4, суб--, мулісекундний гарячий , МSK6, затримка маршруту, МSK7, а за ним - двадцять років виробництва. .. СDK TypScript - це поверхня, яка переносить вихідного коду МSK9 до Node, Applications and Browsers without requiring either to know anything about what happened underneath (не обов 'язково знати нічого про те, що сталося під ним). [Навчитися швидше](/blog/stylobot-release-learning): після того, як відбитки пальців побачили з достатньою впевненістю

<!--category-- TypeScript, Node.js, StyloBot, Bot Detection, Security -->
<datetime class="hidden">2026-06-01T10:30</datetime>

# Введение

Більшість розпізнавання роботів, доступних в екосистемі TypScript, дає вам одне з двох речей - : - відбитки пальців браузера, які показують, чи *клієнт* виглядає автоматизовано, або регекс через Пользователя

Ни одна з них не є корисною на рівні застосування.

[бод](https://github.com/fingerprintjs/BotD) запускається в браузері і не має доступу до заголовків запитів, відбитки пальців TLS , поведінка сеансу МSK2 чи IP репутація M SK3 Безголовний хром і Googlebot виглядають однаково з ним . Пакети, які відповідають чистому стандарту UA | (`is-bot`, `isbot`) ловити тільки те, що дозволяє бути пойманим . Мережа МSK2 продукти на шарі МSK3 Zarządzanie дротом Cloudflare M SK4 DataDome ) блокувати на краю, але не вміти персоналізувати на рівні додатку MSC6 Ви дізнаєтесь, що прохання було заблоковано М SK7 ніколи не було так, щоб прохання, яке прийшло до вашої додатки, мало шансу бути МСК8 ботом і середній ризиковий діапазон М СК9 таким чином ваша сторінка скриньки могла б показати крок тремтіння, а ваші аналітики могли б його виключити M СК10 все без коду помилок М S К11

Те, що StyloBot дає вашій програмі TypScript - це вердикт: задрукований результат з послідовним балом ймовірності роботаM SK1 класифікація (`AiBot`, `Scraper`, `GoodBot`, `MaliciousBot`МСК0 ...), групи ризику , запропонована дія МSK3 і бал загрози мSK4 Те, що ви робите з цим вердиктом, залежить від вас

У цій статті ми проглянемо зразок магазину і покажемо, як це рішення перетворюється на поведінку aplikacji на кожному шарі.

[TOC]

# Вирок

Перш ніж будь-що інше, це те, що приходить.

```ts
// @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'
}
```

Вказівний пункт може рухатись `Medium` ризик, замість блокування його. API може виключити `AiBot` рух з тарифу-limit квоти без підрахунку його проти реальних користувачівM SK1 Заявка для входу може додати доказиMSC2of-праця для `Elevated` ризик, не торкаючись досвіду `Low` відвідувачі з ризиком. Нічого з цього неможливих, якщо результат виявлення є boolним на рівні мережі

Ризикові групи у порядку: `VeryLow`, `Low`, `Elevated`, `Medium`, `High`, `VeryHigh`, `Verified`.

# Установлення

```bash
npm install @stylobot/core @stylobot/node

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

`@stylobot/core` має нульowe залежності від часу запуску. Він містить визначення типів , klient REST МSK2 розшифровувач заголовків M SK3 і ( опціонально MSC5 gRPC М SK6 Він працює в Node МSK7 Deno \ , та Bun без модифікації M SK9

`@stylobot/node` надає програмне забезпечення Express middleware, плагин Fastify , шаблони допоміжні інструментиM SK2 і ін 'єктор вердикта. Це залежить від `@stylobot/core` і нічого іншого в часі запуску.

# Поверхня сервера

## Як вирок досягає вашого інструментарію

Посередник має три режими. Усі три виробляють те ж саме `req.stylobot` форма. Який ви використовуєте залежить від того, де працює детектор вашої інфраструктури

```mermaid
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` режим**: розпізнавання відбувалося вгорі на шлюзі ; посередник читає в 'єковані заголовки без зв' язку з мережею МSK2 Це модель виробництва, коли фронтальна шлюза Caddy чи YARP

**`grpc` режим**: посередник набирає автозаряду прямо через HTTP /2. Найнижча затримка МSK2 не потребує JSON над głową `@grpc/grpc-js`. Шлях інтерфейсу gRPC і деталі розгортання бокового авто знаходяться в [Статья про архітектурну галерею](/blog/sidecar-architecture).

**`api` режим**Запроси : `POST /api/v1/detect` над HTTP/1.1. Далі повертається `reasons` (в залежності від-врахунки детекторівМSK2 та `signals` (повне состояние чорної дошкиM SK1, яке інші режими забувають

Посереднє програмне забезпечення завжди не працює. `req.stylobot` встановлений до припустимого пустого вердикту, і запити продовжується.

## З 'єднати його

**Express:**

```ts
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 }));
```

**Зарядити:**

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

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

Додайте декларацію типу, якщо ви хочете мати встромлений доступ до застегнених запитів:

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

# Вулиці крамниці

Те ж саме зразок магазину з [Artykuł ASP.NET](/blog/behaviour-aware-ux), сторінка товаруM SK1 вирахунок, login , бюллетень МSK4 але в шрифті TypeScript M SK5 Результат виявлення один і той самий MSC6 що змінюється - це те, як поверхня виявляє його .

## Страница 1: Страница про товар

Цінники починають ось тут. Вони хочуть назвати товариM SK1 ціни, і рівня запасівMSC3 Ви хочете каталог, який можна indeksувати пошуковими машинами та бути корисним для людей, не передаваючи коммерческих сигналів систематичному вилову .

*Scenario: знижує ціни `/products/:id`. Він не бачить коду зniżki , не додасть M SK2 до - кнопки картки МSK4 і ніякого сигналу купівлі, який варто використовувати на .*

Керівник маршруту:

```ts
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',
  });
});
```

Шаблона (Handlebars):

```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}}
```

Три візерунки, які тут використовуються. Диференціація КраулераM SK1 `SearchEngine` і `VerifiedBot` отримати структуровані метадані без комерційних сигналів `Challenge` є попередженням , не блоком. Небезпечні сеанси все ще можуть купити МSK2

## Страница 2: Проверка

Автоматизація шахрайства, тестувальники картM SK1 брутовий ваучер- форсирівці , і сценарійні ретриси всі сходяться під час зарахункуMSC4 Метою є оборона в глибині без CAPTCHAМSK5перший досвід для всіхMСК6

*Scenario: ваучерM SK1 спроби bot hits `/checkout`. Він бачить мертвуM SK1 закінчується повідомлення , надсилає медовий чайник M SK3 заповнений бланк , і отримує мовчазне підтвердження без стимулу для повторного спробування MSC5*

```mermaid
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
```

Дорога:

```ts
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);
});
```

Шаблона:

```handlebars
{{#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}}
```

Тут ключовою технікою є відкидування медоносів.. Відповідь про помилки вчить атакующих повторювати. ; мовчазна фальшива підтвердження марнує їхній час. МSK2 і сторінка підтвердження виглядає однаково, чи замовлення було справжнім, чи відкидано.МSK3 Express Checkout як перевага довіри. `<div>` конвертер у спільний панель руль (`{{> honeypot}}`) замість того, щоб повторити блок встромного стилю на кожній захищеній формі

## Страница 3: Заход

Вход є важливим- підтримка територіїM SK1 ТоргівляМSK2 викидка відрізняється від купівлі-продажі МSK3 хибний позитивний результат при купівці може втратити продажи ; пропущена важлива особистість М SK5 атака підтримуки втрачає свій рахунок M SK6 правопорядок може бути жорстокішим

*Scenario: credentialM SK1 закінчується сценарійом `/login`. Він бачить попереджувальне повідомлення , заповнює горшок з медом МSK2 і повертається до `/login/denied` перед тим, як аутентифікувати.*

```ts
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);
});
```

```handlebars
{{#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>
```

Робот бачить попередження перед тим, як відправити. Якщо він так чи інакше відправитьM SK1 медовий сад його спіймає . Якщо якимось чином він уникає медового саду, `verdict.isBot` є кінцевим сервером- перевірка боків перед запуском будь-якої логіки аутентифікації

## Сайт 4:

Шриверки штучного інтелекту є іншою проблемою. МСК0 Вони не намагаються brute МСК1 наполягати на рахунках чи ціних на здобуток. МSK2 вони вживають контент для тренування моделей . Часто вони чесні щодо ідентичності , що означає, що правильна реакція - коммерційна, а не протистояння

*Scenario: GPTBot hits `/newsletter`. Він бачить повідомлення про ліцензію даних замість подписки*

```ts
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');
});
```

```handlebars
{{#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="your@email.com" autocomplete="email" />
  </div>
  <button type="submit" class="btn btn-success">Subscribe</button>
</form>
```

`AiBot` це не просто інший ворожий ярлик, це класифікація, яка дозволяє бізнесу реагувати на нього.

# Справники з шаблонів

## Введення вердикту в шаблони

`sbVerdictInjector` запускає посереднє програмне забезпечення для виявлення і додає вирок `res.locals`:

```ts
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` є доступним в кожному шаблоні. `res.locals.sbVerdictScript` є встромним `<script>` ярлик, що визначає `window.__sb` для компонентів браузера.

## Ручки

Визначати `RISK_ORDER` як тільки на рівні модуля (нижні вправники Nunjucks та EJS користуються однією картою ):

```ts
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);
    },
  },
});
```

У шаблонах:

```handlebars
{{#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}}
```

## Нунюкки

```ts
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());
```

```nunjucks
{% sbgate "Low" %}
  <a href="/checkout/express">Express checkout</a>
{% else %}
  <a href="/checkout">Checkout</a>
{% endsbgate %}
```

## ЕДС

EJS не має блокастерів,, але локальні функції захищають більшість випадків

```ts
// 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();
});
```

```ejs
<% 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>
<% } %>
```

# Поверхня браузера

## Введення вердикту в браузер

Додайте `{{{sbVerdictScript}}}` до вашої головки макету. Він відображається якM SK1

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

Далі завантажити пакет елементів:

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

## `<sb-gate>`: показувати або приховувати контент, в залежності від групи ризику

```html
<!-- 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>`: вибрати перший збіговий випадок

```html
<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>` вибирає першу `<sb-case>` чиї `max-risk` вміщає та приховує решту. Остання `<sb-case>` без `max-risk` вловлення. `<sb-gate>` і `<sb-adapt>` слухати `sb:verdict` і ре-оцінити, чи змінюється вирок після першого малюнкуM SK1

## `<sb-widget>`: сервер

`<sb-gate>` закриває клієнта контенту-замістьМSK1 Якщо вам потрібен робот, щоб отримати інший маркупM SK2 не прихований маркуp, вам потрібно відтворити фрагмент на сервері з контекстом розпізнавання, доступним для шаблону .

`<sb-widget>` читає шаблон Рідкий з лінії вказівок `<template>` елемент, перетворює всі запити на widget на сторінку в один об 'єктM SK1trip, і заміняє себе поверненим HTML :

```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>
```

Променні шаблону: `isBot`, `botProbability`, `confidence`, `botType`, `botName`, `riskBand`, `recommendedAction`, `threatScore`, `threatBand`. плюс будь-яка `vars` ви переходите з сервера-замість при телефонуванні `RenderWidget` безпосередньо.

Все `<sb-widget>` елементи, які під 'єднуються в одному мікрозадачному значку, поєднані в один запит. сторінка з п' ятьма виджетами робить одну круглу

```mermaid
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
```

# Інша таблиця

| Komponenт МSK1 Слой МSK2 Що він робить |
|---|---|---|
| `styloBotMiddleware` | Экспресс МSK1 Прикріплення `req.stylobot` через заголовки / gRPC / REST МSK2
| `styloBotPlugin` | Фастифікувати | Те ж саме МSK2 як плагин для фастифікування з `decorateRequest` |
| `sbVerdictInjector` | Экспресс МSK1 Населення `res.locals.sbVerdict` і `sbVerdictScript` |
| Допоміжні вказівники на панелі дотиків | Шаблони сервера МSK2 `sbGate`, `sbIsBot`, `sbBotType`, `sbMinRisk` допоміжні блоки |
| Поширення Nunjucks | Шаблони для серверів МSK2 `{% sbgate "Low" %}...{% endsbgate %}` |
| локальні EJS | Шаблони серверів МSK2 `sbBelowRisk()`, `sbAboveRisk()`, `sbIsBot()`, `sbBotIs()` |
| `<sb-gate>` | Переводник | Помічати МSK2 приховувати контент за групи ризику `sb:verdict` |
| `<sb-adapt>` | Веб-браузер | Виберіть перший збіговий випадок з списку МSK2
| `<sb-widget>` | Веб-браузер
| `StyloBotGrpcClient` | Мережа МSK1 Бан МSK2 Дено | Прямий gRPC-клиент для не мSK4з середнім програмним забезпеченням M|

Далі в релізовій серії: [**Архітектура Sidecar**](/blog/sidecar-architecture), як детектор з 'єднується з не--. NET-стаками , Go SDK МSK3 плагином Caddy M SK4 і інтерфейсом gRPC, який об' єднує його з собою .

Якщо ви прибули сюди з боку .NET , [Подібність-Усвідомлення ASP.NET UI](/blog/behaviour-aware-ux) об 'єднує ті ж самі моделі фронту магазину з допомогою помічників та фільтрів дій; реновація надійності, яка зберігає пам' ять двигунаM SK1 стабільність під постійним рухом [Винайти та виправити безмежний ріст у довгостроковій-Running M SK1NET Services](/blog/stylobot-release-reliability). Живий двигун , панель управления МSK2 та коммерціальні прилади [stylobot.net](https://www.stylobot.net).