Back to "StyloBot 系列发布@:行为 @-}Aware typeScript UI"

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 系列发布@:行为 @-}Aware typeScript UI

Monday, 01 June 2026

此文章显示StyloBot'}分类结果如何在 Script}:}服务器中继器为快件和快件中的应用逻辑@,}模板辅助器

StyloBot

缩略 @stylobot/core@stylobot/node npm 软件包不久将发布@. @% 下面的片段描述它们将暴露的表面@. @

StyloBot 释放系列

  1. 行为@, @% 不是身份为何StyloBot以行为方式模拟客户?
  2. ASP=.@NET 用户界面@ : 服务器@ MS K1 {. NET 应用程序的表面=% 1}
  3. 在LongM-Running .NET服务中查找和固定无约束增长使引擎在生产过程中无趣的可靠性纪律
  4. 檔案型態Script UI此篇文章 :
  5. 侧车建筑@ : 检测引擎如何连接到非@ MS K1NET 堆栈
  6. 学习加快速度“:”适应性学习系统“, 4 -”记忆和裁决缓存
  7. 静坐不动的测试: 校验纪律 @: 一个 BDF 文件驱动回溯@,load @MS K3和校准
  8. StyloExptracte {-} 本地学习 HTML 到 标记下转换器: HTML→ Markdown 层与探测器对齐 MSSK2 行尸虫清晰识别捕捉了, 和狗食环让它诚实

StyloBot'}[email protected] CoreMSMK2 高端- 性能框架,其优异的原始元素=, supp*-milli second holk}-Patrent latencyMS K7和生产20年pedigree 后面的StyolobotoMSKOT+.STymbript SDK是将引擎's输出引入NodeMSOK10js应用程序和浏览器的表面,无需了解任何有关在“.” {(热-path lantenge来自涵盖的适应学习系统。 学习加快速度@: 一旦一个指纹被足够自信地看到 \ ,\ 裁决缓存跳过整个探测器管道和答案 在微秒@.)

一. 导言导言 导言 导言 导言

在 TypeScript 生态系统中,大多数的机器人检测都给了你两样东西之一: 一个浏览器指纹可以告诉你是否 客户 在用户@- @Agent 字符串上看起来是自动的\ ,}或正则, 只捕捉宣布自己为的机器人字符串

应用层也无用 @.

立方体 在浏览器中运行, 且无法查看请求信头@, TLS 指纹 @,会话行为@MS K2或IP名声@, 或IP 名声 @MS K3 无头的铬和 Googlebot 看上去与它完全一样.纯 UA 匹配的软件包 @ (}is-bot, isbot) 捕捉到的只是被抓住的选择 @. 网络@- 游戏产品 @MS K3Cloudflare Bot 管理}, DataDome) 區塊,但無法在應用層有個性化 {.你得知一個請求被封,從來沒有到達您的應用程式有「0.87boat概率」 和中度風險波段MS K9,所以你的檢查頁本可以顯示摩擦步骤,而你的解析器可能已經排除了它,所有沒有錯誤代碼的請求 *.}

StyloBot 赋予您 TypeScript 应用程序的是一个判决@ : @ 输入结果, 一个连续的 Bot 概率评分@ MPK1_ 分类 @ MS K2}AiBot, Scraper, GoodBot, MaliciousBot@, ...), 风险带, 推荐的行动, 和威胁评分.

此文章通过一个抽样商店前端, 并显示该裁决如何成为每个层的应用程序行为@ : @ server middware @ MS K1}# 模板助手@ MPK2 和浏览器组件@ I.}

判决

万事如意,这就是我们到达的地方

// @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 登录表可以添加证明@- @of_-}工作 Elevated 风险,而不会触及 Low 如果检测结果为网络层的布丁,则无法做到这一点 .

风险波段按顺序排列 : VeryLow, Low, Elevated, Medium, High, VeryHigh, Verified.

安装装置

npm install @stylobot/core @stylobot/node

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

@stylobot/core 它包含定义类型@, REST client_, 标题派, 和 @( optionally}) gRPC clidient. 它在Node, DenoMS K8 与 Bun 中工作,没有修改.

@stylobot/node 提供快速快捷插件@,}模板辅助工具 *,和判决注射器=. 它取决于 @stylobot/core 在运行时没有别的东西... .

服务器表面

判决如何达到你的中器

中间器件有三种模式.}所有三个产品都一样 req.stylobot 形状@.}您使用的哪个取决于基础设施检测运行中的位置 @:

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 模式模式@: 检测在网关上向上游跑 ;中器读到注射头,没有网络呼叫. 这是Caddy或YARP门坐在前面时的生产模式*MS K3

grpc 模式模式中继器直接呼叫侧车 在HTTP/2.}低档Lowest lantency*, 不需要JSON高架机 , @grpc/grpc-jsGRPC接口和侧车部署细节载于 侧面车架建筑文章.

api 模式模式# :呼叫# POST /api/v1/detect HTTP/1.1.}返回 reasons (per-检测器捐款})和 signals @ ( @ 整个黑板状态@ MS K1} 其他模式忽略了 @ MPK2 @

中间软件总是不能打开@ . @ 如果侧车无法到达或乘数超过@,} req.stylobot 设定为允许的空判结果,请求继续@. @%

装配起来

特快讯号0

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

快速化@ : @%

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

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

添加类型声明, 如果您想要在快速化请求中键入访问权限@: @%

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

店铺

同一样品仓库 ASP.NET 文章“,”产品页面@,Q 校验“, login}, 通讯 @, 但是在Script. Type Scrippt}检测结果是一样的#;什么变化是表面如何暴露它的吗?.

产品页面

他们想要产品名称 , 价格 MS K2 和库存水平 用搜索引擎为目录编制索引 对人类有用

假想: 切价点击 /products/:id没有折扣代码 , 没有加号 MS K2 @to@-}卡特按钮 @, 也没有值得使用“.”的购买信号

路由处理器:

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@):}#

{{! 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}}

在此使用的三种模式 @ . @ Crawler create=: SearchEngineVerifiedBot 获取没有商业信号的结构化元数据@, 这正是他们真正需要的 @. 贴现目标: 一个只显示为低Q-}风险流量的忠诚代码 Challenge 是一个警告,不是一个街区MS K1可疑会话仍然可以买到.

页面@2:检出

欺诈性自动化@,卡测试器 @,券brute}-forrs,和脚本的重写在校验时都汇合.。目标是深度防御,没有CAPTCHAMS K5每个人的第一体验_.#

*假想@: @ a passer % 1} 测试机器人点击率 /checkout“. ” 它看到一条已死的消息“- end message , 提交一个蜂蜜罐 -填充的表格$, 并收到一份无声确认, 没有重试奖励@.

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

路线@:

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

模板@:

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

蜜罐丢弃是这里的关键技术@. @ 错误反馈教育攻击者变换@ ;} 一个静默的假确认浪费了他们的时间\ ,_ 和确认页面看起来完全一样, 无论该命令是否真实或被抛弃@ I. Express check outp as a trust betweat @ MS K4 not a defair { ) 意指只有赢得短路的会话才能在生产过程中获得更短的路 #.}%,乘以蜂池 <div> 折叠成共享的处理栏部分@ ( @ {{> honeypot}}@)_ 而不是在每一个受保护的窗体上重复内嵌样式块@.}

页面=3:登入

登入为 credicialial@- @stuffing 领土\ .}交易=-off 不同于支票_:}校验时的假正数可能丢失一份销售; 错过了一次证书=MSSK5}填充攻击会失去一个账户 <. >

假想@: @ a credicial=% - statusing 脚本点击 /login它看到了一个威慑信息 ,}填满了蜂蜜罐 ~MS K2}然后被弹射到 /login/denied 在任何认证运行前@. @%

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>

机器人在提交“.”之前先看到威慑力量,如果它提出反正“MS K1” 它会抓住蜂蜜罐 如果它能以某种方式避免蜂蜜壶‘,’ verdict.isBot 是所有验证逻辑运行前的最后服务器@ - @ side check

《邮报》

AI爬行者是一个不同的问题 @.他们不是试图brete}-Force账户或收成价格@;它们正在摄入示范培训的内容#. 他们通常对身份很诚实$,,这意味着正确的回应是商业上的而不是对抗性的.

GPTBot 点击率 /newsletter@.#它看到的是数据许可信息, 而不是订阅声道@.}它的提交被静悄悄地丢弃.#

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 不只是另一个敌对标签@; @ it is a classion 使得企业能够做出响应.}一个许可信息比它告诉操作员他们正在处理什么,并给予他们一项行动来采取\NSK3}

模板助手器

将判决输入模板

sbVerdictInjector 在探测中器之后运行 并做出判决 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 每个模板中都可用@. @% res.locals.sbVerdictScript 是一个内嵌 <script> 标签设置 window.__sb 用于浏览器组件@. @%

处理栏

定义界定 RISK_ORDER 在以下模块范围 {(}Nunjucks 和 EJS 辅助器使用同一地图=):}

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

在模板中 @:

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

努纳克人

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 没有块状助推器,, 但本地功能覆盖大多数案例

// 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>
<% } %>

浏览器表面

将判决输入浏览器

添加新增 {{{sbVerdictScript}}} 在您的布局头上@ . @ it makes as\ MS K1}

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

然后装入元素捆绑\ :

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

<sb-gate>@:_ 按风险带显示或隐藏内容

<!-- 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>: 选择第一个匹配的大小写

<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 并重新评价第一个涂料后是否改变判决@.}

<sb-widget>:伺服器 @- 液态碎片

<sb-gate> 隐藏内容客户端@-side.}如果您需要机器人来接收不同的标记 @,}不是隐藏的标记{,} 您需要在服务器上以模板可用的检测环境提供碎片.}

<sb-widget> 从内含读取液体模板 <template> 元素 , 将页面上的所有部件请求分解为单圆\ MS K1 trip,, 并用返回的 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 您在调用时从服务器传出@ - @%side RenderWidget 直接的.

全部 <sb-widget> 连接同一微时针的元素会被分解成一个请求@. @% 1个有五个部件的页面, 一个可以生成一个回合@ -} trip_ :}

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

总表

#元件 @ _图层@ }它做了什么?
styloBotMiddleware * M }快讯 ∮ 速报 ∮ ∮ 随员 req.stylobot 通过标题 @ /# gRPC #/_ REST * *
styloBotPlugin 快速化 @ Same@,作为快捷插件 decorateRequest
sbVerdictInjector }快讯 ∮ ∮ 弹出 res.locals.sbVerdictsbVerdictScript
+Handlebars 帮手 @ +Server模板@ sbGate, sbIsBot, sbBotType, sbMinRisk 助推器 @ @%
@ Nunjucks 扩展 { } 服务器模板@ {% sbgate "Low" %}...{% endsbgate %}
EJS 本地用户 @ 服务器模板@ sbBelowRisk(), sbAboveRisk(), sbIsBot(), sbBotIs()
<sb-gate> }浏览器 @ }根据风险波段显示#/}hiide 内容@,}收听 sb:verdict
<sb-adapt> 浏览器 @ 从列表中选择第一个匹配的大小写@
<sb-widget> 瀏覽器 @ 抓取伺服器@- 已產生液體碎片, 批批量 { }
StyloBotGrpcClient 節點 / bun MS K2 Deno 直接 gRPC 客戶端, 用于非@- middleware 使用 @

下一则发布序列中的“:” 侧车建筑“,” 检测引擎如何连接到非“-.NET Tatts,GO SDKMS K3Cddy插件{,}和将它连接在一起的 gRPC界面@.}

如果你从".NET"抵达这里 ASP=.@NET 用户界面 使用标签辅助器和动作过滤器覆盖相同的商店前端模式@;}保持引擎内存的可靠性重新工作=-}稳定在持续流量下 在LongM-Running .NET服务中查找和固定无约束增长.+Live 引擎 @,_QTaptro@,#和商用控制 stylobot% . @ net.

logo

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