# @dxv-systems/measurement 中文文档

规范地址：https://npmcn.com/package/%40dxv-systems/measurement/

版本：0\.11\.1
npm 元数据更新时间：2026-11-09T19:47:11.240Z
中文译文更新时间：2026-11-13T02:05:21.198Z
软件包许可：MIT

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40dxv-systems/measurement
原文对照：https://npmcn.com/package/%40dxv-systems/measurement/?lang=original
项目主页：https://github.com/dxv-systems/dxv-packages#readme
源码仓库：https://github.com/dxv-systems/dxv-packages

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：140 kB，32 个文件
- 模块格式：CommonJS
- TypeScript 类型：包内自带
- Node.js 要求：\>=24\.0\.0
- 运行依赖：1 个
- peer 依赖：react \>=18
- 首次发布：2026-09-29T12:14:13.534Z
- 最新版本发布：2026-11-09T19:47:10.942Z
- 近 30 天下载量：6486（2026-10-13 至 2026-11-11，npm 官方下载统计）
- 已发布版本数：21（近 12 个月 21 个）

## 中文简介

1个同意门(Consent Mode v2),1个扇出GA4,Meta,PostHog和TikTok,以及一个总是报告的Metis接力腿

## 安装

```sh
npm install @dxv-systems/measurement
```

也可以使用：`pnpm add @dxv-systems/measurement`、`yarn add @dxv-systems/measurement`、`bun add @dxv-systems/measurement`

## README

# @dxv-系统/计量

一个同意门,一个扇出, 和一个梅蒂斯腿总是报告.

-   **同意**: Google Monse Mode v2 有4个类别,一个横幅,饼干镜像服务器所读.
-   **风扇出**: 一个键入 `track()` 至GA4,Meta Pixel和PostHog,每个门都由店内的政策所决定. TikTok 像素被装入并发送同一门下的页面视图, 但没有收到 `track()` 事件.
-   **梅蒂斯语Name**数字 : `reportPurchase` 编号 : `reportLead` 将每个转换到Metis中继器, 携带访客的同意.

导入此软件包而不是 `@dxv-systems/metis-client` 直接:一个应用程序从metis-client那里需要的每分每分每分每分每分都在这里被再出,所以没有代码路径可以发送到Metis,而不通过同意映射.

```bash
npm i @dxv-systems/measurement
```

| 子路径 | 运行在 | 目录 |
| --- | --- | --- |
| `@dxv-systems/measurement` | 任何地方(浏览器安全) | 同意状态,读取/写入/保存, `pushConsentUpdate`, (中文(简体) ), `toMetisConsent`, 策略, 内置脚本构建器, 浏览器点击- id 抓取 |
| `@dxv-systems/measurement/react` | 浏览器 | `ConsentBootstrap`, (中文(简体) ), `ConsentBanner`, (中文(简体) ), `useConsent`, (中文(简体) ), `useBannerCategories`, (中文(简体) ), `ClickIdCapture`, (中文(简体) ), `createTracker` |
| `@dxv-systems/measurement/next` | 下一个代理/ 中间软件 | `persistClickIds` |
| `@dxv-systems/measurement/server` | 节点 | `reportPurchase`, (中文(简体) ), `reportLead`, (中文(简体) ), `readMeasurementCookies` |

## 规则

**总是向梅蒂斯报告。 同意决定转发;Metis不决定储存.**

每一次采购和线索都交给梅蒂斯 不管访客回答什么 同意只决定两点:

1.  **活动的内容.** 仅当访客同意时,才会发送电子邮件/电话和点击ID `ad_user_data` 财务报告和审定财务报表 `ad_personalization`。否则,事件是裸露的:命令或领导、其价值和同意旗帜.
2.  **梅蒂斯是否可以转发** 给梅塔和谷歌 Metis只当两面旗帜都是真的,来访者决定时才向前.

Metis 存储它收到的每一个事件。 这就是允许它计算同意门下降的转换次数的原因.

## 政策

每个店铺决定,每个目的地,是只经同意才放火还是永远放火. 软件包编码了这个决定;它从来没有做到.

```ts
import { definePolicy } from "@dxv-systems/measurement";

// Anything not named stays strict (gated on consent).
export const policy = definePolicy({ meta: "always" });
```

| 目标 | 严格( 默认) | `"always"` |
| --- | --- | --- |
| `ga4` | 仅与 `analytics_storage` | 事件总是; gtag.js 仍然应用“ 同意” 模式( 拒绝时没有小写) |
| `posthog` | 仅装入和捕获 `analytics_storage` | 衡量每个访客: `analytics_storage` 通常(饼干, `identify`,重放),没有它无饼干(`cookieless_mode: "on_reject"`,没有 cookie 或浏览器存储,只有记起拒绝的旗帜) |
| `sessionReplay` | 仅使用 `analytics_storage` (并且只有在邮轮运行时) | 永远 |
| `meta` | `fbq('consent','revoke')` 没有事件 `ad_storage` | `fbq('consent','grant')` 无论答案是什么 |
| `tiktok` | 仅装入像素 `ad_storage` | 立即装入 |

`STRICT_POLICY` 是每个目的地的门。 一个共同的姿态是 `definePolicy({ ga4: "always", posthog: "always" })`:GA4和PostHog量出每位访客,而Meta,TikTok和会话重放则遵循访客的回答. 店主决定店主的姿势.

**`posthog: "always"` 需要 PostHog 项目设置“ Cookieless 服务器散列模式”** (项目设置). 没有它 PostHog 放弃了无饼干事件, 因此拒绝或永远不回答的访客保持隐形. 有了政策, `createTracker` 选择没有 `analytics_storage` 明确(`on_reject` 将“从未回答”视为选择退出”,在他们同意时选择加入,只开始重播 `analytics_storage`,以及 `identify` (发送诸如电子邮件地址等特征)也需要 `analytics_storage`。 。 。 .

Meta 和 TikTok 像素已打开 `ad_storage` 单独(`hasPixelConsent`:这是非Google像素的饼干同意. `ad_user_data` 财务报告和审定财务报表 `ad_personalization` 是Google 同意模式的信号,关于向Google发送数据,gtag.js应用它们本身. `hasAdConsent` (单位:千美元)`ad_storage` + 键 `ad_user_data`为Google-ads特定检查保存,不是像素门.

**班纳复制件必须和这道门相符.** 选项释放不同的东西. `ad_storage` 单独打开浏览器像素和饼干(Google Ads,Meta,TikTok),因此其选项必须给这些像素取名并说出他们收到的东西. `ad_user_data` 财务报告和审定财务报表 `ad_personalization` 一起允许服务器侧共享:散列的电子邮件地址和电话号码外加点击ID,通过Metis发送,可能将转换转发给Google和Meta(它们是Google自己的同意模式信号). 因此,这两个选项必须命名为Google和Meta,并说发送只在两个选项都打开时才会发生;用它们作为仅由Google描述的不足. `DUTCH_BANNER_COPY` 做这个,测试把它钉住。 它还说电子邮件地址和电话号码通过Google标签到Google;一个没有Google Ads标签的站点(没有) `shareUserDataWithGoogle`(a) 通过 `copy={dutchBannerCopy({ googleTag: false })}` (0.5.3) 因此,旗帜的保证不会超过发生. 自0.5.4起,该文本是按网站内容构建的: `dutchBannerCopy({ pixels, forwardsTo, contactTags })` 名称像素 `ad_storage` 打开时,每个平台Metis都会向前(Google,Meta,Pinterest)和每个获得散列联系方式的浏览器标签. 每个站点公布名单一次,并保持与梅蒂斯所开的相等:一个没有命名的平台不被同意. 自0.6.0起,旗子可以取自Metis的平台列表取而代之(`platformsEndpoint`,参见"哪个平台的横幅名称":然后它用答案存储被命名的平台,并在Metis添加一个时再次询问. 无 `platformsEndpoint` 当文本更改时, 横幅本身不会再次问: 存储的答案仍然算数, 因此在切换新平台之前, 重新询问( 新的同意存储密钥) , 或者只转发给文本更改后给出的答案 。 一家商店经过自己的 `copy` 必须信守同样的承诺;否则拒绝"共享数据"的访客仍然有发送到像素事件或服务器侧转换,这并非知情同意.

同意模式 `update` 发送到gtag的每个答案,不管政策如何:它是信号本身,而不是目的地。 点击 ID 只能用 `ad_storage`无论政策如何.

## Next.js (App路由器) (英语)

`lib/measurement.ts`,由布局和客户端组件共享:

```ts
import { definePolicy } from "@dxv-systems/measurement";
import { createTracker } from "@dxv-systems/measurement/react";

export const policy = definePolicy({});
export const tracker = createTracker({
  policy,
  ga4MeasurementId: process.env.NEXT_PUBLIC_GA4_ID,
  posthog: process.env.NEXT_PUBLIC_POSTHOG_KEY
    ? {
        key: process.env.NEXT_PUBLIC_POSTHOG_KEY,
        load: () => import("posthog-js").then((m) => m.default),
        options: { api_host: "/ingest", ui_host: "https://eu.posthog.com" },
      }
    : undefined,
});
```

根地布局. 靴子必须先来 任何标签:

```tsx
import Script from "next/script";
import { ga4ScriptSrc, metaPixelScript, tiktokPixelScript } from "@dxv-systems/measurement";
import { ClickIdCapture, ConsentBanner, ConsentBootstrap } from "@dxv-systems/measurement/react";
import { policy } from "@/lib/measurement";

<html>
  <head>
    <ConsentBootstrap ga4MeasurementId={GA4_ID} />
  </head>
  <body>
    {children}
    <Script src={ga4ScriptSrc(GA4_ID)} strategy="afterInteractive" />
    <Script id="meta-pixel" strategy="afterInteractive">{metaPixelScript(META_ID, policy)}</Script>
    <Script id="tiktok-pixel" strategy="afterInteractive">{tiktokPixelScript(TIKTOK_ID, policy)}</Script>
    <ConsentBanner policy={policy} />
    <ClickIdCapture />
  </body>
</html>
```

`ConsentBanner` 财务报告和审定财务报表 `ClickIdCapture` (b) 只在属于(不在工作人员网页上)的地方提供。 横幅的复制默认为荷兰语;通过 `copy` (如从下入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入出入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入出出出入入入入入入入出出出出出出出出出出出出出出出入出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出 它的默认类是 Tailwind, 所以将软件包添加到您的源中(`@source "../node_modules/@dxv-systems/measurement/dist";`)或通过 `classNames`。一个页脚链接打开它 `openConsentSettings()`。 。 。 .

`proxy.ts` (或 `middleware.ts`:

```ts
import { persistClickIds } from "@dxv-systems/measurement/next";

export default function proxy(req: NextRequest) {
  const res = NextResponse.next();
  persistClickIds(req, res); // writes dxv_click_ids only when the consent cookie grants ad_storage
  return res;
}
```

晚收者由 `ClickIdCapture`将身份记在记忆中 `ad_storage`。 。 。 .

从客户部分跟踪:

```ts
tracker.track("add_to_cart", { item, cartValueCents });
tracker.trackPurchase({ orderNumber: order.number, items, valueCents, taxCents, shippingCents, discountCents });
tracker.trackLead({ leadId: lead.id, source: "offerte" });
```

`trackPurchase` 财务报告和审定财务报表 `trackLead` 给 Meta 和 PostHog 一个确定事件 ID (`purchaseEventUuid(orderNumber)` 编号 : `leadEventUuid(leadId)`) (中文(简体) ). 服务器报告从同一个密钥获得相同的ID,因此平台去除浏览器和服务器事件. **这个 `orderNumber` 编号 : `leadId` 在浏览器中必须等于 `order.id` 编号 : `lead.id` 在服务器上.**

服务器,在创建命令或引导的路由或服务器动作中:

```ts
import { cookies } from "next/headers";
import { readMeasurementCookies, reportPurchase } from "@dxv-systems/measurement/server";

const { consent, clickIds } = readMeasurementCookies(await cookies());
// Store { consent, clickIds } with the order: a payment webhook arrives without the shopper's cookies.
await reportPurchase({
  order: { id: order.number, totalCents: order.totalCents, currency: "EUR" },
  customer: { email: order.email, phone: order.phone },
  consent,
  clickIds,
});
```

## Vite + 快递

单位 `index.html`,在标记之前,插入输出 `consentBootstrapScript({ ga4MeasurementId })` (单位:美元) `transformIndexHtml` 钩子,或制成 `<ConsentBootstrap />` 如果 shell 已交付服务器的话 。 用同样的方式添加像素 `metaPixelScript` 编号 : `tiktokPixelScript`,或者跳过应用程序不使用的那些。 在反应应用程序中,挂载 `ConsentBanner` 财务报告和审定财务报表 `ClickIdCapture` 一次,并使用 `createTracker` 如上所示.

快捷键 :

```ts
import { readMeasurementCookies, reportLead } from "@dxv-systems/measurement/server";

app.post("/api/lead", async (req, res) => {
  const lead = await saveLead(req.body);
  const { consent, clickIds } = readMeasurementCookies(req.headers.cookie);
  void reportLead({ lead: { id: String(lead.id) }, customer: { email: lead.email }, consent, clickIds });
  res.status(201).json({ id: lead.id });
});
```

在无服务器的运行时间, `await` 报告改为: `void`:一个脱去的许诺可以随功能被冻结.

在职业页面上的工作申请是领先的 `kind: "application"` (单位:千美元)`reportLead({ lead: { id, kind: "application" }, ... })`,metis-client 0.15.0):Metis计算,从不转发到广告平台. 不要打电话 `tracker.trackLead` ,或者浏览器像素向Meta报告它毕竟是一个线索.

TikTok活动(`ttq.track` 对于购买,线索等)不发送:TikTok像素被装入并只发送页面视图. 它们是以后可能增加的.

## 服务器报告

`reportPurchase` 财务报告和审定财务报表 `reportLead`数字 :

-   读取 `DXV_CUSTOMER_ID`, (中文(简体) ), `DXV_CONVERSIONS_RELAY_SECRET` 财务报告和审定财务报表 `DXV_CONVERSIONS_RELAY_URL` (默认) `https://metis.dxvagentic.com`),除非 `relay` 通过;
-   和那些失踪的,什么都不做和日志 **一个** 每个进程发出警告;
-   永远不要扔。 他们回来了 `{ status: "sent" | "skipped" | "failed" }` 和日志失败(通过) `logger` (b) 利用自己的资源;
-   通过 metis 客户端重试 5xxx (1s, 2s, 4s) , 因此在死继电器上的预算可达~7秒.

`consent` 因此,打电话者不能忘记:

| 你通过 | 手段 | 梅蒂斯看见了 |
| --- | --- | --- |
| (单位:千美元) `ConsentState` | 访客回答 | `{ adUserData, adPersonalization }` 从答案 |
| `null` | 没有同意饼干:访客从未回答过 | `{ adUserData: false, adPersonalization: false, decided: false }` |
| `NO_CONSENT_MECHANISM` | 完全没有同意的旗帜 | 无同意反对 |

### 更好的匹配 (0. 3. 0) (中文(简体) )

Metis向Meta和Google转发一个转换器,带有它携带的任何标识符,所以发送请求所知道的. 所有东西都丢掉,像散货一样 除非访客同意 `ad_user_data` 财务报告和审定财务报表 `ad_personalization`数字 :

```ts
import { readMeasurementCookies, reportPurchase, requestContext } from "@dxv-systems/measurement/server";

const { consent, clickIds, browserIds } = readMeasurementCookies(req.headers.cookie);
await reportPurchase({
  order, consent, clickIds,
  customer: { email, phone },
  phoneCountryCode: "31",                                        // "06 …" becomes +31 for Meta and Google
  request: { ...requestContext(req.headers), ...browserIds },   // IP, user agent, Meta _fbc/_fbp
  eventSourceUrl: pageUrl,                                       // without its query string, and only with consent
});
```

存储 `request` 与您存储的顺序 `consent` 财务报告和审定财务报表 `clickIds`: 支付网胡克没有.

**销售人员进入** (电话,电子邮件,聊天)没有浏览器和横幅. 报告他们 `reportOfflinePurchase({ order, customer, phoneCountryCode, actionSource: "phone_call" | "email" | "chat" | "physical_store" | "other" })`:没有同意反对,包括散列。 Metis只保存并转发给操作员证明该应用的离线销售. 网站的销售被拒绝,通过 `reportPurchase` 经访客同意.

### 漏斗步骤服务器侧 (0. 4. 0)

Metis也向Meta转发了三个浏览步骤: `view_item` (视频), `add_to_cart` (添加)和 `begin_checkout` (启动检查). 给追踪者一个你自己的路线,并和处理者一起服务:

```ts
// lib/measurement.ts
createTracker({ policy, funnelEndpoint: "/api/measurement/funnel" });

// app/api/measurement/funnel/route.ts
import { funnelRouteHandler } from "@dxv-systems/measurement/server";
export const POST = funnelRouteHandler();
```

在Meta同意下,跟踪器会给像素拨打随机事件ID,并将同一步骤(event id,值,项,页)发布到该路由. 处理者读了同意饼干,Meta's `_fbc`编号 :`_fbp`,IP和用户代理,并报告 `reportFunnelEvent`未经广告同意,它什么也没有发出(Metis会放弃),回答204。 Meta去掉了浏览器和服务器在共享id上的事件. 漏斗步骤在梅蒂斯语中从未被算作转换. 路由从相同的环境变量中取取中继设置 `reportPurchase`。 。 。 .

**博茨被丢弃 (0. 11.0).** 从已知的bot, craper, 链接预览器, 无头浏览器或被脚本客户端(`isBotUserAgent` 从 `@dxv-systems/metis-client`: Googlebot, AdsBot, Bingbot, facebook Externalhit, Headless Chrome, callow, wget, python- requests and more),或完全没有用户代理,被解答204而无所发送; `reportFunnelEvent` 接电话 `{ status: "skipped", reason: "bot" }`,在读取同意之前。 一个使页面成形的爬行者,否则会把假步骤送到Meta. 采购和线索从不过滤:它们来自已付款的订单和发送的表格。 用户代理是处理器使用的唯一的bot信号,因此在路由前也设置了每个IP的速率限制.

**App提示 (0. 11.0).** 设定 `DXV_METIS_APP_ID` (或 `relay.appId`在Metis的应用程序ID中,每个报告和同意平台都读作 `X-DXV-App-Id`所以Metis先尝试那个应用的秘密 可选;没有它,没有什么变化.

## Google标签:客户的电子邮件和电话 (0.5.0)

Google 强化了线索转换功能,与 DXV Metis 上传的已散发的电子邮件和手机匹配 **在网站上看到Google标签** 在广告点击旁边。 没有它,Google Ads就报告"没有用户提供的数据匹配",并且只上传带有一个 `gclid` 计数。 当有人在浏览器中订购或发送表格时调用此选项:

```ts
import { shareUserDataWithGoogle } from "@dxv-systems/measurement";

shareUserDataWithGoogle(
  { email: form.email, phone: form.phone, phoneCountryCode: "31" }, // "06 …" becomes +31…
  { adsId: process.env.NEXT_PUBLIC_GOOGLE_ADS_ID },                // "AW-…"
);
```

它设置 `user_data` (少发电子邮件,E.164电话) `form_submit` 给广告号码 那个事件的名称 **无转换标签**,所以它算不上:永远不要在Metis旁边添加浏览器转换,或者每次销售都算两次. 未经双方的广告同意,它一无所事(`ad_user_data` 财务报告和审定财务报表 `ad_personalization`),同样的规则梅蒂斯向前下. 在Google Ads中,开启对线索的强化转换(方法"Google Ads API")和Google标签"Form interactive". `phoneE164(phone, countryCode?)` 用于需要E.164表格的其他任何出口.

## 特快:漏斗路线(0.5

`funnelRouteHandler` 是一条下一条路由; Express获得相同的行为:

```ts
import { funnelExpressHandler } from "@dxv-systems/measurement/server";
app.post("/api/metis/funnel", express.json(), funnelExpressHandler());
```

它读取了解析后的身体(`req.body`),同意饼干,Meta's `_fbc`编号 :`_fbp`,IP和用户代理,报告该步骤 `reportFunnelEvent` 并回答204(400), 它会像下一条路线一样 降低机器人的步子 以分分分分分入前.

两个管事的人都喜欢 `relay`, (中文(简体) ), `logger`, (中文(简体) ), `fetchImpl` 页:1 `sleepMs`具有自己时限的路线 `sleepMs: async () => {}` 故复试勿等.

## 哪个平台的横幅名称( 6. 0)

一个商店从不硬代码 广告平台的横幅名称。 横幅询问商店自己的服务器,它询问Metis哪个平台转发了这个应用程序的转换. 答案在征得同意后被存储,每给梅蒂斯的报告都会携带,而梅蒂斯只转发给访客所看到的被命名的平台.

**1\. 上行路线.** 下一个( App 路由器), `app/api/measurement/consent-platforms/route.ts`数字 :

```ts
import { consentPlatformsHandler } from "@dxv-systems/measurement/server";
export const GET = consentPlatformsHandler();
```

接下来的 15 个, 以后每个请求运行一个 GET 路由; 在 下 14 个同时添加 `export const dynamic = "force-dynamic";` 所以名单不是在建设时冻结的.

快捷键 :

```ts
import { consentPlatformsExpressHandler } from "@dxv-systems/measurement/server";
app.get("/api/measurement/consent-platforms", consentPlatformsExpressHandler());
```

都从 `DXV_CUSTOMER_ID`, (中文(简体) ), `DXV_CONVERSIONS_RELAY_SECRET` 财务报告和审定财务报表 `DXV_CONVERSIONS_RELAY_URL` (或拿 `relay`, (中文(简体) ), `logger` 财务报告和审定财务报表 `fetchImpl` 像漏斗处理器一样) `{ platforms: ("google" | "meta" | "pinterest")[], version }` 与 `Cache-Control: public, max-age=300`。Metis 的答案被缓存5分钟。 当Metis不能被问到时(中继嵌入缺失,Metis倒下或拒绝),他们回答每个支持的平台, `{ platforms: ["google", "meta", "pinterest"], version: "fallback" }` 与 `max-age=60`,因此横幅会过度分出而非分出不足. 无出品门:预览会显示真实列表.

**2.将路线通向横幅.**

```tsx
<ConsentBanner
  policy={policy}
  platformsEndpoint="/api/measurement/consent-platforms"
  copyOptions={{ pixels: ["Google Ads", "Meta"], contactTags: ["Google"] }}
/>
```

横幅获取路径 (1 第二次超时; 失败时, 答慢, 或路径的) `fallback` 回答它命名每个支持的平台,但不再问任何人,因为这个列表是一个猜测,而不是梅蒂斯的词)并用它构建其文本 `dutchBannerCopy({ ...copyOptions, forwardsTo })`,平台名称被写为Google,Meta和Pinterest. 一个也经过的地方 `copy` 保留自己的字符串和类别,但两个共享选项除外(`ad_user_data`, (中文(简体) ), `ad_personalization`),它总是来自已建成的列表. 在路由解答之前,一个未回复的横幅名称每个被支持的平台.

**储存了什么.** 答案为: `consent_v2` 财务报告和审定财务报表 `dxv_consent_v2` 收益 `platforms`,列出命名的旗帜(`writeConsent(state, policy, { platforms })`; 读回来用 `readConsentRecord()`) (中文(简体) ). 没有答案 `platforms` (自0.6.0起,或无 `platformsEndpoint`)保持有效并算作Google和Meta. `readConsent()` 仍然只返回四类.

**又问.** 当路由命名一个平台时,存储的答案缺乏(`consentCoversPlatforms(stored, current)`),横幅再打开,预填了访客更早的选择. 拒绝两个共享选项的访客不再被问及:没有平台从他们那里收到任何信息,包括一个新的平台. 从未回答过的访客会像以前一样看到横幅.

**Metis得到的东西.** `readMeasurementCookies` 返回存储 `platforms` 征得同意,以及 `toMetisConsent` 把它们放在中继器上 `consent.platforms` 页:1 `reportPurchase`, (中文(简体) ), `reportLead`, (中文(简体) ), `reportFunnelEvent` 还有漏斗路线 没有列表的答案会发出否 `platforms`,而Metis则将自己的规则用于旧的答案.

**隐私页面将同一平台命名为(0.6.1).** 隐私声明说谁得到了数据,提供 `<ConsentPlatformList endpoint="/api/measurement/consent-platforms" />` (从) `@dxv-systems/measurement/react`· ; `joiner="of"` 用于"Google, Meta of Pinterest". 它使每个被支持的平台都成为第一,因此页面读取正确,没有JavaScript和过-而不是过-,在路由用一个答案时在Metis的列表中互换.

## 转写或定制旗帜(0.7.0)

荷兰建筑师只帮助一家接受荷兰文字的商店. 另两种形状保持同样的承诺(标语名称是Metis转发的平台

**你自己的话: `copy` 作为职能.** 它获得平台名称( E)`"Google"`, (中文(简体) ), `"Meta"`, (中文(简体) ), `"Pinterest"`; 每一个支持到路由解答为止)及其结果在各地都获胜,包含两个共享选项. 您拥有文本, 翻译, 并且必须指定您得到的每个平台 . `formatPlatformList(names, { locale: "nl" | "en", joiner: "and" | "or" })` 加入他们("Google, Meta and Pinterest").

```tsx
// next-intl: messages hold "... sent to {platforms} ..."
const t = useTranslations("consent");
const locale = useLocale() === "nl" ? "nl" : "en";

<ConsentBanner
  policy={policy}
  platformsEndpoint="/api/measurement/consent-platforms"
  copy={(platforms) => bannerCopy(t, formatPlatformList(platforms, { locale }))}
/>
```

`bannerCopy(t, platforms)` 是店里自己的地图员:它通过了 `{ platforms }` 改为 `t(...)` 页:1 `ad_user_data` 财务报告和审定财务报表 `ad_personalization`。当 Metis 转发到无平台时,函数接收 `[]` 和横幅隐藏两个共享选项,所以不需要空列表大小写; `ConsentPlatformList` 因此,将隐私句子包裹在您可以隐藏的元素中(或者只有在您自己的支票上这样写时才使句子生效)。 一个对象 `copy` 行为如前: 已建的荷兰共享选项仍然取代您的选项 。 隐私页面需要 `<ConsentPlatformList endpoint="..." locale="en" />` (单位:千美元)`joiner="or"` 为"或". `formatPlatformList` 从软件包根和从 `/react`。 。 。 .

**你自己的旗帜.** 以 `useConsentPlatforms(endpoint)`,以决定是否与 `shouldReaskForPlatforms`,并用答案存储列表:

```tsx
import { readConsentRecord, shouldReaskForPlatforms, writeConsent } from "@dxv-systems/measurement";
import { useConsentPlatforms } from "@dxv-systems/measurement/react";

const listed = useConsentPlatforms("/api/measurement/consent-platforms"); // { platforms, settled }
const names = listed.platforms.map(toDisplayName); // name every one of them in your text

useEffect(() => {
  if (shouldReaskForPlatforms(readConsentRecord(), listed)) setOpen(true);
}, [listed]);

function save(state: ConsentState) {
  writeConsent(state, policy, { platforms: listed.platforms });
}
```

`useConsentPlatforms` 返回每个支持的平台 `settled: false` 直至路由回复,当它失败时,在路由倒倒而无终点;同样的过度披露横幅使用. 当Metis回答它转发给任何人(一个没有广告账户连接的商店)时,它返回 `{ platforms: [], settled: true }`: 自定义横幅后隐藏 `ad_user_data` 财务报告和审定财务报表 `ad_personalization` 选项和存储两者均被拒绝 `writeConsent(state, policy, { platforms: [] })`。 。 。 . `shouldReaskForPlatforms(record, listed)` 旗帜是自己的规则: `true` 只针对共享的访客(`ad_user_data` 或者说 `ad_personalization` 并且其存储的答案现在缺乏一个平台Metis的名字;从来没有在未解决的名单上,从来没有拒绝。 记录的答案 `platforms: []` 从未显示共享选项, 因此其拒绝不是拒绝: 一旦Metis指定一个平台, 它就会再次询问 。 过 `listed.platforms` 改为 `writeConsent` 仅当您的文本命名它们时; 没有列表, 答案将算作 Google 和 Meta .

## 只询问网站需要什么( 0. 0)

只有在设备上的非必要存储或访问(电子通信第5条第3款,Telecontractiewet 11.7a)和与广告平台共享个人数据时才需要征得同意。 一个没有这种标记的站点,其转换的Metis在任何地方前行都不需要横幅. 其隐私页面会保留:通报义务并不依赖于一面横幅.

**`loads`:在浏览器中声明同意加注的站点加载标记.** 关键是政策目的地(`ga4`, (中文(简体) ), `posthog`, (中文(简体) ), `sessionReplay`, (中文(简体) ), `meta`, (中文(简体) ), `tiktok`+) 加上 (中文(简体) ) `googleAds`,带有Google Ads id的Google标签(显示于 `ad_storage` 因此它没有政策条目。 横幅只问需要什么,用 `bannerCategories({ loads, policy, platforms })` 从软件包根 :

| 类别 | 显示时间 |
| --- | --- |
| `analytics_storage` | `loads` 已经 `ga4`, (中文(简体) ), `posthog` 或者说 `sessionReplay` 其政策不是 `"always"` |
| `ad_storage` | `loads` 已经 `meta` 或者说 `tiktok` 其政策不是 `"always"`,或 `googleAds`,或 Metis 转发到一个平台(点击码 cookie 服务于转发) |
| `ad_user_data`, (中文(简体) ), `ad_personalization` | Metis 转发到一个平台( 如 0. 7. 0) |

目的地在 `"always"` 不计数:它运行时没有访客的回答(GA4和PostHog随后运行为无饼干,参见Policy). "只有分析"只显示在另一个类别旁边. "接受所有","拒绝"和"拯救选择"覆盖显示的内容;未显示的内容被存储为否定.

**没有分类,没有旗帜.** `ConsentBanner` 之后就无动于衷了 `openConsentSettings()`,并且不写给同意饼干或本地Storage。 未经决定,每道门都关上,所以没有起火. 隐藏页脚的“ Cookie 设置” 链接方式相同 `useBannerCategories({ policy, loads, platformsEndpoint })` 从 `/react`数字 : `null` 当平台列表即将出现时,然后是类别(`[]` =没有横幅).

**其等入单.** 与 `loads`,横幅只让平台路线回答或放弃(最多1秒),所以不会闪出. 美地士落地了,慢答,路线倒倒了还是不倒 `platformsEndpoint` 都意味着每个支持的平台, 所以横幅显示广告类别: 过度披露,像以前一样。 不需要横幅的网站需要路由 回答 `[]`。 。 。 .

**又问.** 没有横幅的访客没有答案,所以横幅只是在某事需要同意后出现(Metis给一个平台起名,或者网站给它添加了标签) `loads`) (中文(简体) ). 与 `loads`,一个答案将存储它被问到的类别(`categories` 内 `consent_v2`;不是在饼干,服务器不需要它). 何时 `loads` 后来需要一个从未问过答案的类别,横幅再次打开,预填(`shouldReaskForCategories(readConsentRecord(), needed)` 为自定义横幅). 这也适用于分析,即使是拒绝其他一切的来访者:他们从未被问及新的目的,所以他们的沉默并不是拒绝。 他们所见到和拒绝的类别再也没有被问到(直到答复到期)。 平台列表保持了0.6.0规则(`shouldReaskForPlatforms`:当Metis添加一个平台时,不再询问共享选项的拒绝.

**无 `loads`** 横幅表现为 0.7. 0 : 每个类别, 当 Metis 真正转发到无时隐藏的共享选项 .

**隐私链接.** `privacyHref` 在横幅正文后建立真正的链接, 文本 `copy.privacyLink` (荷兰默认为"私隐"; `PRIVACY_LINK_TEXT.en` “读取隐私声明”。 链接得到 `classNames.privacyLink`。 。 。 .

没有分析、没有广告标签和Metis平台的网站(路线答案) `[]`:没有横幅,也没有页脚链接.

```tsx
const banner = { policy, loads: [], platformsEndpoint: "/api/measurement/consent-platforms" } as const;
<ConsentBanner {...banner} privacyHref="/privacy" />

const categories = useBannerCategories(banner); // in the footer
{categories?.length ? <button onClick={openConsentSettings}>Cookie-instellingen</button> : null}
```

一个带有PostHog(严格政策)且没有Metis平台的网站提出了一个问题,即分析;一旦Metis命名了一个平台,它也会问广告类别:

```tsx
<ConsentBanner
  policy={policy}
  loads={["posthog"]}
  platformsEndpoint="/api/measurement/consent-platforms"
  privacyHref="/privacy"
/>
```

与 `loads`,荷兰正体名称仅为所问目的(“ cookies voor analysis” ); 对象或函数 `copy` 仍然赢了.

## 客户身份、姓名和地址(0.9.0)

需求 `@dxv-systems/metis-client` 0.14.0 或以后(一种依赖,没有安装). `customer` 打开 `reportPurchase`, (中文(简体) ), `reportLead`, (中文(简体) ), `reportFunnelEvent` 财务报告和审定财务报表 `reportOfflinePurchase` 还要 `externalId`, (中文(简体) ), `firstName`, (中文(简体) ), `lastName`, (中文(简体) ), `city`, (中文(简体) ), `postalCode` 财务报告和审定财务报表 `country` (ISO α-2):

```ts
await reportPurchase({
  order, consent, clickIds,
  customer: {
    email, phone,
    externalId: order.customerId,                  // the shop's own customer (or lead) id
    firstName: order.billing.firstName,
    lastName: order.billing.lastName,
    city: order.billing.city,
    postalCode: order.billing.postalCode,
    country: order.billing.country,                // "NL"
  },
});
```

他们的大门和电子邮件和电话完全一样:只有在来访者同意时才发出 `ad_user_data` 财务报告和审定财务报表 `ad_personalization`Metis只把它们传给平台上 被命名的同意 Metis-clients常态化并草率化;邮政和乡村代码也通俗化,因为Google拿走了那些未被遮盖的(参见metis-client README). 在您存储时按顺序保存 `consent` 财务报告和审定财务报表 `clickIds` 如果报告发生在网页上.

点击ID 抓取 (`ClickIdCapture`, (中文(简体) ), `persistClickIds`现在也保留 `ttclid`, (中文(简体) ), `msclkid` 财务报告和审定财务报表 `epik`,列在同一标题下 `ad_storage` 门和一触即发的规矩, 并算作报酬 `paidTouch`没有发送 `ttclid` 或者说 `msclkid` 到 TikTok 或 微软 。 Metis 保存给后期转发器 。 饼干在隐私声明中的描述应该给它们取名.

## 这是付费广告吗?

Metis同意下放监视器的分拆将(被重新使用和未决定)转换成付费的,有机的和未知的,所以商店可以看到它输给横幅的访客是否是它所付的广告. 这需要一个布尔每个转换, `paidTouch`。 。 。 .

-   `ClickIdCapture` 从登陆 URL 记录一次每页负载(`gclid`, (中文(简体) ), `fbclid`, (中文(简体) ), `gbraid`, (中文(简体) ), `wbraid`, (中文(简体) ), `epik`, (中文(简体) ), `ttclid`, (中文(简体) ), `msclkid`,或已支付 `utm_medium` 例如 `cpc` 或者说 `paid_social`),无论同意. 读出来 `getPaidTouch()` 从 `@dxv-systems/measurement`数字 : `true`, (中文(简体) ), `false`,或 `undefined` 在捕捉之前.
-   **只有记忆.** 值存在于模块变量中。 它从未被写入到一个饼干,本地Storage,SessionStorage或其他任何东西,因此没有设备存储访问(ePrivacy 5(3)),不需要任何同意. 点击ID本身没有发送。 Metis数起国旗,从不向广告平台转发. 如果输入存储, 测试失败 .
-   **自己传给你的服务器**,带有铅或订单有效载荷,然后进入 `reportLead` 编号 : `reportPurchase`。服务器不会从 cookie 中推断(没有):

```ts
// browser
import { getPaidTouch } from "@dxv-systems/measurement";
await fetch("/api/lead", { method: "POST", body: JSON.stringify({ ...form, paidTouch: getPaidTouch() }) });

// server
void reportLead({ lead, consent, clickIds, paidTouch: typeof body.paidTouch === "boolean" ? body.paidTouch : undefined });
```

-   **`false` 意思是"有机地着陆",从来没有"这页没有抛物".** URL 中的付费信号 `true`。 。 。 . `false` 仅当页面负载被证明是登陆时才被记录: 平面导航(Navigation Timing type) `navigate`)与空出或跨出处裁判. 重装、后发、前发、前发、同源参考器(同一站点的另一页)或没有导航时间的浏览器留下值 `undefined` (未知),再从事件中省去国旗. 客户端导航保留了第一个值. 后序:在满载或回访后,或按一后所发之令,所发之旗相去通常不详,故所发之饷被低估为未知,从来为有机. 付费的车厢总是获胜, 即使在重装.
-   在店内隐私说明中提及:一个用于测量的集合,内存的起落页信号,基于合法权益,不存储在设备上.

## 同意的答案持续多久

答复有效期为12个月(`CONSENT_VALIDITY_DAYS`页:1 监管者通常指在再次询问访客之前6至13个月 `setConsentValidityDays(days)` 启动时(如果在服务器上建立内在脚本,则也在服务器上),因为这些脚本嵌入了时期.

答案位于两个地方,本地Storage `consent_v2` 和饼干 `dxv_consent_v2` 服务器代码读取, 并同时携带一个 `decidedAt` 时间戳。 它们一起过期: 一个新的决定 设定饼干的最大年龄到整个时期。 每个浏览器读取( O)`readConsent`, (中文(简体) ), `useConsent`, (中文(简体) ), `ConsentBanner`, (中文(简体) ), `ClickIdCapture`包重新检查它们:

-   缺少的或不同的饼干从本地Storage重写,决定作为最大年龄仍然有效;
-   将过期的决定从两者中去除,所以横幅再次问起,而大门又被否定;
-   由0.1.0存储的答案没有 `decidedAt`已经按现在的决定盖了章,所以升级时没有人会再次出现.

内置脚本( A)`consentBootstrapScript`,像素片段)跳过一个已过期的答案。 旧饼干没有 `decidedAt` 仍在服务器上分析 .

## Metis同意的下线监视器怎么看的

Metis从每个事件的四个州中得一个 `consent` 对象 :

| 状态 | 线 | 上面写着 |
| --- | --- | --- |
| `consented` | 两面旗帜都是真的 | 可转发至广告平台 |
| `refused` | 存在和决定的对象,但不同时真实(包括部分和只分析) | 客人说不行 |
| `undecided` | `decided: false` | 访客从不接电话 |
| `not_recorded` | 无对象 | 发件人不报告同意:整合差距 |

监视器的下降率被拒绝 外加所有事件都未经决定 `not_recorded` 分别显示. 这个软件包上的店从来不生产 `not_recorded` 除非它经过 `NO_CONSENT_MECHANISM`。它不能看到像素侧损失(GA4,Meta Pixel,PostHog在浏览器中),只有中继事件.

## 常见问题

### @dxv-systems/measurement 是什么？

1个同意门\(Consent Mode v2\),1个扇出GA4,Meta,PostHog和TikTok,以及一个总是报告的Metis接力腿

### 如何安装 @dxv-systems/measurement？

在项目目录运行 npm install @dxv-systems/measurement，或使用 pnpm add / yarn add / bun add。

### @dxv-systems/measurement 支持 TypeScript 吗？

支持。包内自带 TypeScript 类型声明（package\.json 声明了 types 入口），安装后即可获得类型提示，无需再安装 @types 包。

### @dxv-systems/measurement 支持 ESM 还是 CommonJS？

以 CommonJS 形式发布，可以直接 require\(\)；在 ES Module 项目中也可以用 import 引入（Node\.js 会把 module\.exports 作为默认导出）。

### @dxv-systems/measurement 需要什么版本的 Node\.js？

package\.json 的 engines 字段要求 Node\.js \>=24\.0\.0。版本不符时 npm 默认只给出警告，开启 engine-strict 后会拒绝安装。

### @dxv-systems/measurement 可以商用吗？

@dxv-systems/measurement 使用 MIT 许可。宽松许可：允许商业使用、修改和再分发，通常只需在分发时保留原版权与许可声明。以上为概括，不构成法律意见，请以许可原文为准。

### @dxv-systems/measurement 的下载量有多少？

根据 npm 官方下载统计，@dxv-systems/measurement 在 2026/10/13 – 2026/11/11 这 30 天内被下载 6,486 次，日均约 216 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入，适合横向比较热度，不能直接等同于使用人数。

### @dxv-systems/measurement 还在维护吗？

最新版本 0\.11\.1 发布于 2026/11/10（3 天前）；至今共发布 21 个版本，其中过去 12 个月发布了 21 个；首次发布于 2026/09/29。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

