@aranova/ 跟踪- 反应
Aranova客户网站的反应跟踪和同意设施.
安装
npm install @aranova/tracking-react
设置
创建共享跟踪模块并导入范围 TrackingProvider 编号 : useTracking 从那个模块 整个您的应用程序.
// src/lib/tracking.ts
import { createTracking } from "@aranova/tracking-react";
import { ARANOVA_TRACKING_CONFIG } from "./aranova-services";
export const { TrackingProvider, useTracking } = createTracking({
apiKey: import.meta.env.VITE_ARANOVA_TRACKING_API_KEY,
endpoint: import.meta.env.VITE_ARANOVA_TRACKING_ENDPOINT,
trackingConfig: ARANOVA_TRACKING_CONFIG,
triggers: {
automatic: {
page_view: {},
time_on_site: { thresholdSeconds: 60 },
specific_page_visit: {
pages: [
{ name: "contact_page", pathPattern: /^\/(contact|book|get-started)/ },
{ name: "google_ads_service_page", pathPattern: /^\/services\/google-ads\/?$/ },
],
},
},
manual: {
form_submit: {},
phone_click: {},
cta_click: {},
},
},
debug: import.meta.env.MODE !== "production",
});
在您反应树根上挂载提供者 .
// src/main.tsx
import { createRoot } from "react-dom/client";
import { App } from "./App";
import { TrackingProvider } from "./lib/tracking";
createRoot(document.getElementById("root")!).render(
<TrackingProvider>
<App />
</TrackingProvider>,
);
遗留的静态 gtag 标识
gtagId, (中文(简体) ), gtagIds, (中文(简体) ), GoogleAdsTracking,并生成 ARANOVA_GTAG_IDS 保留
相容性 APIs. 不要用于新安装: 静态ID不能消耗仪表板
标记更改或墓碑而不进行重新部署。 如果维持一个遗留装置, gtagIds
装入每个条目并优先于 gtagId。 。 。 .
<TrackingProvider gtagIds={{ production: "AW-111111111", test: "AW-222222222" }}>
<App />
</TrackingProvider>
手动事件
手动活动必须在 triggers.manual 在此之前 trackEvent() 接受他们.
import { useTracking } from "./lib/tracking";
export function LeadForm() {
const tracking = useTracking();
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
const form = event.currentTarget;
const data = new FormData(form);
tracking.trackEvent("form_submit", {
form: {
id: form.id,
action: form.getAttribute("action"),
fields: [
{
name: "service_interest",
type: "select",
label: "Service interest",
value: String(data.get("service_interest") ?? ""),
},
{
name: "is_existing_patient",
type: "checkbox",
label: "Existing patient",
value: data.get("is_existing_patient") === "on",
},
],
},
page: { path: window.location.pathname },
});
}
return (
<form id="lead-form" action="/api/lead" onSubmit={handleSubmit}>
{/* fields */}
</form>
);
}
fields[].value 可以是任何 JSON 值:字符串、数字、布尔、无效、数组或对象。 值必须是JSON-可序列化的,因为事件被存储为一党JSONB. 有意提交的牵头字段可能包括生名,电子邮件,电话号码,地址,选取,自由文本消息,以及提交用于第一当事方分析及牵头操作的文件数据. 从提交表单中明确构建数组; SDK 从未刮出任意 DOM 字段 . File编号 :Blob 对象必须转换为 JSON 表示; 单独上传更大的文件并发送其存储引用。 事件元数据上没有执行字节限制——但是在导航冲出之前,一个提交文件已发射 keepalive,它的浏览器在64 KB附近并静静地下降,所以保持内嵌的有效载荷小. 从不发送密码,认证符,付款卡/银行证书,或私人密钥. 适用客户的隐私通知、同意、保留和规范的数据要求。 Google离线匹配使用正常的,服务器侧 SHA-256-hashed标识符——而不是原始的自由文本/文件元数据.
电话点击数 (tel: • 手动或自动抓取
选择到 自动抓取 和每个 tel: 链接被跟踪,没有每个链接代码。 添加
autoCapture 页:1 phone_click 登记 —— 由单一授权的点击听器进行其余:
manual: {
phone_click: { autoCapture: {} }, // default selector: a[href^="tel:"]
// or narrow it: { autoCapture: { selector: "a.call" } }
},
<a href="tel:+14165550199" data-aranova-section="header">
(416) 555-0199
</a>
数字是从 href (已正式改为E.164); section 来源于可选
data-aranova-section。 。 。 . 与 cta_click页:1 phone_click 还炒了Google广告
转换 联系电话目标(a) CLICK_TO_CALL ——自动点火
窃听器被捕获了,所以你从不打电话 trackConversion (firring需要销售客户 与电线
相同的配置;见下文).
喜欢用仪器连接自己吗? 从一个点击处理器中发射它 同样的方式:
tracking.trackEvent("phone_click", {
phone_number: "+14165550199",
page: { path: window.location.pathname },
section: "header",
});
CTA 点击- 手动或自动抓取
开火 cta_click 完全控制这个名字:
tracking.trackEvent("cta_click", {
cta_name: "Book appointment",
section: "hero",
destination_url: "/booking",
page: { path: window.location.pathname },
});
或者选择加入 自动抓取 并跳过每个按钮代码。 添加 autoCapture 页:1
cta_click 注册——此选项包含一个获得授权的点击听器:
manual: {
cta_click: { autoCapture: {} }, // default selector: [data-aranova-cta]
// or target existing classes: { autoCapture: { selector: "a.cta, .btn-primary" } }
},
然后标记您的 CTA 在标记中—— 没有进口, 没有处理器 :
<a href="/booking" data-aranova-cta="Book now" data-aranova-section="hero">Book now</a>
<button data-aranova-cta="Get a quote">Get a quote</button>
cta_name 决定: data-aranova-cta 值, 否则元素的修剪文本
(封顶120个字符),其他 tag#id。 。 。 . section 来源于可选 data-aranova-section· ;
href编号 :destination_url 和一个短 element 描述器是自动捕获的。 点
真实的 CTAs 选择器( 一个明确的属性是安全默认) — 每次匹配点击
计数,包括后面的 stopPropagation。 。 。 .
自动居中+退出( E)page_exit(中文(简体) )
page_exit 自动捕获——没有注册。 在 SPA 导航、标签隐藏和
页面卸载它记录 活动 (可见)花在页面上的时间加上最大卷轴
深度已经达到,通过一个恒定信标交付,所以最后一页仍然算数. 这个权力
每页寄存和寄存在仪表板分析分页中;没有配置.
键入的铅表单
生成网站的铅格式合同一次并通过它发送,所以需要放弃
字段失败 tsc 而不是丢失线索 :
npx @aranova/tracking-cli forms gen # writes src/aranova-lead-forms.ts; `forms check` in CI
import { createLeadFormSubmit, slotFields } from "@aranova/tracking-react";
import { ARANOVA_LEAD_FORMS } from "@/aranova-lead-forms";
const leadForms = createLeadFormSubmit(useTracking(), ARANOVA_LEAD_FORMS);
leadForms.submit({
formId: "booking-form",
kind: "booking",
fields: { given_name: values.firstName, phone: values.phone, ...slotFields(values.slot) },
});
开发记录每项违反合同行为, debug 扔出, 生产发送, 无论如何标记
form.contract_violations。 手建不整齐 form_submit 用于生产
(标记) invalid_shape而不是放弃。 全向导 :
文件/跟踪-包装/铅形式。 。 。 .
提交后的可选问题(病人类型、时间、他们是如何听到的)附在后面的
领头 握着手柄 submit() 返回并传递给 addDetails() 从谢幕.
那个屏幕是 <section> 有按钮,永远不要 <form>: 表格记录它自己的
form_start (在漏斗中从未提交表格)并在Enter上提交.
const { submission } = leadForms.submit({ formId: "booking-form", kind: "booking", fields });
setSubmission(submission); // then render the post-submit <section>
// "Add to my request" button's onClick: every role is optional here, nothing is owed.
leadForms.addDetails(submission, {
fields: { patient_type: answers.patientType, note: answers.note, email: answers.email },
extra: [{ key: "heard_from", label: "How did you hear about us?", value: answers.heardFrom }],
}); // { sent: false, reason: "no_fields" } when every answer is blank
电话字段
捆绑 libphonenumber-js: 剖析/格式 utils + a React 输入. 显示可配置
发送到后端的值是 总是E.164配置一次通过 createTracking({ phone: { defaultCountry: 'CA', display: 'national' } })。 。 。 .
import { usePhoneField, PhoneField, toE164 } from "@aranova/tracking-react";
const phone = usePhoneField(); // phone.value (display), phone.e164 (wire), .isValid, .error
<input {...phone.inputProps} />; // or the batteries-included <PhoneField name="phone" />
toE164("416-555-0199"); // '+14165550199' (null if invalid)
纯,同位素的同位素也有 @aranova/tracking-react/phone (无回放. 全向导 :
电话:md。 。 。 .
记录销售
记录销售/转换(一等可变资源——不是火灾和遗忘)
活动)。 货币为整数小单位(美分); currency 是一个需要的 ISO-4217 enum.
一个异形 createSalesClient (在根入)服务双方——什么是
键可能由后端执行,而不是通过隐藏方法。 浏览器写入方式
公开 键 :
import { createSalesClient, toMinor } from "@aranova/tracking-react";
const sales = createSalesClient({
apiKey: import.meta.env.VITE_ARANOVA_TRACKING_API_KEY,
endpoint,
});
await sales.record({ currency: "CAD", amount_total_cents: toMinor(250, "CAD"), service: "tires" });
读取/ 完整 CRUD 需要 隐藏 键,并且必须运行服务器端——从不运送
浏览器捆绑中的密钥。 在 Vite + Vercel 应用程序中,在无服务器中保存
函数。 从 /sales 子路径——它是无反应的,所以服务器
捆绑从不拉入提供者/组件 :
// api/sales.ts (Vercel serverless function — runs on the server)
import { createSalesClient } from "@aranova/tracking-react/sales";
const sales = createSalesClient({
apiKey: process.env.ARANOVA_TRACKING_SECRET_KEY!,
endpoint: process.env.ARANOVA_TRACKING_ENDPOINT!,
});
export default async function handler(_req, res) {
res.json(await sales.list({ limit: 50 }));
}
密钥读取电源仪表板(所有货币组合——从未跨货币总和):
await sales.summary({ range: "mtd", timezone: "America/Toronto", compare_to: "previous_period" });
await sales.list({ sort: "amount_total_cents", want_total: true }); // → { items, total_count, … }
await sales.customers.list({ segment: "returning", sort: "total_spent" }); // phone-keyed roster
await sales.customers.summary({ range: "mtd" });
const cfg = await sales.business.config(); // tz / currencies / services (pk or sk)
summary() 记分日历/关税范围, granularity,以及期末
compare_to; 遗产 24h/7d/30d 不变。 A 类 顾客是他们的电话(E.164) —— 说 customers.*
是一个活人名册,没有单独的表格。 任何读数的公用钥匙客户端都会收到 403。 。 。 .
根项@aranova/tracking-react仍在再出口 createSalesClient 编辑
货币/日期帮助者进行反相匹配,因此现有的进口继续发挥作用——但更愿意 /sales
在服务器代码中, 反应表面不会到达您的服务器捆绑 .
生成类型 AranovaService 由您与 CLI 的仪表板服务合并
(装作 依赖性:
npm install --save-dev @aranova/tracking-cli
npx @aranova/tracking-cli gen # reads ARANOVA_TRACKING_SECRET_KEY from .env
见完整指南: 销售追踪 和CLI参考文献: (单位:千美元)。 。 。 .
订货交割(订货一体化)
当商业书籍通过一个集成(今日AutoLeap), 已出版的跟踪配置
携带 integrations.autoleap.booking。在牵头表格成功提交之后(和
form_submit 提供可选的“选择时间”步骤:
import { BookingHandoffLink } from "@aranova/tracking-react";
import { ARANOVA_TRACKING_CONFIG } from "@/aranova-services";
<BookingHandoffLink
trackingConfig={ARANOVA_TRACKING_CONFIG}
fallbackUrl="https://myalp.io/…" // baked last resort when no config has loaded
prefill={{ firstName, lastName, phone, email }}
>
Pick your time
</BookingHandoffLink>;
- 链接是商店自己的订书页 从现场配置, 否则
fallbackUrl; 它会使 没有,除非是https://myalp.io/…电话以数字形式发出. - 它显示,即使
enabled虚假的:顾客总是有预订的方法. useBookingIntegration(trackingConfig)返回现场提供者(或在配置前无效) ),来自共享配置运行时间(没有额外获取) :enabled, (中文(简体) ),fallbackUrl, (中文(简体) ),acceptsBookings, (中文(简体) ),timezone, (中文(简体) ),configVersion, (中文(简体) ),rules财务报告和审定财务报表options。 。 。 .getBookingIntegration(integrations)财务报告和审定财务报表buildBookingHandoffUrl(url, prefill)这是 反应自由等同.- 报告
booking_handoff能力.
网站预订(订书一体化)
何时 acceptsBookings 网站可以通过Aranova直接订入店铺系统
API (英语). SDK是无头;在站点自己的设计系统中构建出形式:
import { useBookingIntegration, useBookingSlots, useBookingSubmit } from "@aranova/tracking-react";
const booking = useBookingIntegration(ARANOVA_TRACKING_CONFIG);
const days = useBookingSlots(ARANOVA_TRACKING_CONFIG); // [{ date, weekday, morning, afternoon }]
const { status, outcome, submit } = useBookingSubmit(conversions.bookings, {
formSubmit: {
tracking,
formId: "booking-form",
// Your own questions, for our reporting only; never sent to the shop.
extraFields: [
{ name: "heardVia", type: "select", label: "How did you hear about us?", value: heardVia },
],
},
});
await submit({
startDate: "2026-10-06",
startHour: 9,
firstName,
lastName,
phone,
email,
vehicle: { year: 2019, make: "Toyota", model: "RAV4" },
dropOff: "dropOff", // only when booking.rules.dropOffOptions is non-empty
services: ["brakes"], // keys from booking.options
notes,
marketingSmsConsent, // the customer's own unchecked opt-in; never default it on
});
conversions是一个网站createSalesClient({ apiKey, endpoint, firing: getTrackingConfigRuntime(...) })· ;.bookings阅读规则 从运行时间.outcome.status数字 :created(商店的短信和电子邮件给客户本身;不发送 你们自己人,pending(同样成功;仍在发送中,如果失败,则提醒所有者;status转弯pending一旦服务器这么说, 而最后的答案是民意调查,failed(成功副本加outcome.fallbackUrl,预填的商店页面,invalid(单位:千美元)outcome.message没有寄出去,stale(时代已变;相貌被回放).outcome.mode(和booking.mode在提交之前:live每个真正的客户;test(同意店铺测试铅源)或simulated(从来没有)在测试访问或 非生产 API. 显示一个小的“ 测试预订” 注释, 当它不是live。 。 。 .- 仅限开发:
useBookingSubmit(bookings, { simulate: "failed" })强制模拟 结果created, (中文(简体) ),rejected, (中文(简体) ),unknown, (中文(简体) ),failed)来锻炼每个屏幕。 生产 API无视. 本地测试针对本地 API 财务报告和审定财务报表 本地CDN; SDK 警告在 当终点是本地主机时控制台,但配置来自生产. booking.options按商店服务类别分类:group(标题, 或者说null)和可选description。 。 。 .groupBookingOptions(booking.options)返回时[{ group, options }]因此,将每组一个标题放在它的下面.useBookingSlots编号 :bookableSlots(rules, { timeZone })提供入口的启动时间, 根据店里的日期判断.useBookingSubmit通过双击重用一个尝试键或 重试一次,然后送来form_submit一次:不要为这个表格寄出自己的表格. 这是一个 类型booking提交(ADR-0024):每个字段都有合同role, (中文(简体) ),service这是 选项密钥,preferred_date实值YYYY-MM-DD, (中文(简体) ),preferred_time实值{ start: "HH:00", end: null },以及drop_off实值wait-for-it编号 :drop-off中添加表单的问题formSubmit.extraFields; 预订已报告的名称( q)phone, (中文(简体) ),email,.)被丢弃.- 分别询问姓氏,并使用手机号码(店内确认文字去取).
- 预订后的详情( 当
booking.rules.vehicleDeferred,用于额外笔记,或网站 回答:updateBooking({ vehicle, notes, heardVia })从同一个钩子。 它决定updated编号 :queued编号 :notified_owner(全部已保存)或invalid, (中文(简体) ),mismatch, (中文(简体) ),conflict, (中文(简体) ),not_found, (中文(简体) ),failed(仅限failed值得重试。 要活下来的重载, 保存booked(单位:千美元){ bookingId, attemptKey }(中文(简体) ) 内sessionStorage并传回:updateBooking(details, saved)。 。 。 .heardVia只有报告 永远不能到达店铺 它的值和表型相同heardViaform_submit字段, 因此,在成功屏幕上问它 而不是表格报告相同。 每预订一次更新, 所以用延迟的车辆而不是第二次呼叫. - 报告
booking_integration能力.
现场转换射击
运行 tracking-cli gen 一次发射 ARANOVA_TRACKING_CONFIG,然后使用这个
google追踪系统已初始化。 R2是权威的: SDK确认
发送 Google 标签、页面视图或转换命令之前的当前对象。 盘子
标记更改、目标再映射和废旧状态墓碑
public, max-age=60, must-revalidate 没有运行或站点重新部署.
import {
createSalesClient,
createTracking,
getTrackingConfigRuntime,
toMinor,
} from "@aranova/tracking-react";
import {
ARANOVA_TRACKING_CONFIG,
type AranovaConversion,
type AranovaService,
} from "./aranova-services";
const googleTracking = getTrackingConfigRuntime(ARANOVA_TRACKING_CONFIG);
export const { TrackingProvider, useTracking } = createTracking({
apiKey,
endpoint,
triggers,
trackingConfig: ARANOVA_TRACKING_CONFIG,
});
// Bind the generated unions so `service` and `trackConversion` keys are type-checked.
const sales = createSalesClient<AranovaService, AranovaConversion>({
apiKey,
endpoint,
firing: googleTracking,
});
await sales.recordSale({
currency: "CAD",
amount_total_cents: toMinor(250, "CAD"),
service: "tires",
}); // records + fires
- 自动事件目标 当其注册的探测器越过公布的阈值时起火.
- 销售+人工目标 使用上面的共享运行时间。 射击是经过同意的, 交易已编辑,并且只浏览器.
手动转换(多形式) : 一次登记触发器,然后呼叫 trackConversion(key) 每个表格的提交处理器。 关键出自仪表板目标分页(活动目标有: form_submit编号 :phone_click编号 :cta_click 触发器已映射到WEBPAGE 动作),并作为 AranovaConversion 工会 gen 因此,他们自动完成并拒绝打字:
// createTracking({ …, triggers: { manual: { form_submit: {} } } }) // register once
// in the contact form's onSubmit:
sales.trackConversion("contact_form"); // ✅ typed by AranovaConversion
// in the demo form's onSubmit:
sales.trackConversion("demo_request");
sales.trackConversion("typo"); // ❌ compile error — not a known goal key
每一次呼叫火灾 仅限 用于这一目标的WEBPGE转换(无) /sales 写入); SDK
如果球门没有被标定的话,就不要了 重映射已存在的密钥不需要代码 。 A 类 新手册
目标密钥 仍然需要 tracking-cli gen 要刷新 AranovaConversion,加站点处理器
接线. ARANOVA_CONFIG_URL 财务报告和审定财务报表 conversionConfig: { cdnUrl } 遗留的相容性
API; 不用于新安装 。 见
转换-配置-schema.md。 。 。 .
ARANOVA_TRACKING_CONFIG 携带商业身份, SDK 构成对象 URL
反对生产CDN—— endpoint 选项只移动 摄入事件,而不是配置
获取。 要读取来自其它地方的配置( 本地对象商店), 设置
REACT_APP_ARANOVA_CDN_BASE_URL;生成的模块已经读取并返回到
因此不需要更改代码。 把它放在生产中.
在手动进球之前,
gen发出空ARANOVA_CONVERSIONS这样AranovaConversion决定:never约束它(createSalesClient<AranovaService, AranovaConversion>然后让 每个trackConversion调用编译错误。 直到你画出至少一个手动的入球 重新运行gen,仅绑定服务通用(createSalesClient<AranovaService>(…); 转换键默认值返回到string。 。 。 .
同意(v2——选择退出模式)
追踪是 默认: gtag,"Meta"像素,以及现场转换的射击全部从第一颜料上跑出,除非访客已存储了明确而未到期的下降. 降级是值得的 90天时间 (然后,来访者转而接受违约赠款);明确的补助金从未到期。) 没有 pending 状态和包件船 未经同意 ——每个网站提供自己的脚步"cookie偏好"控制并披露其隐私政策中的跟踪(该通知是使得选择退出模式可以防守的原因).
脚下控制 - useCookiePreferences()
import { useCookiePreferences } from "@aranova/tracking-react";
function CookiePreferences() {
const { isDenied, isDefault, optOut, optIn } = useCookiePreferences();
return isDenied ? (
<button onClick={optIn}>Enable ad measurement</button>
) : (
<button onClick={optOut}>Opt out of ad measurement</button>
);
}
钩子返回有效 state (单位:千美元)"granted" | "denied"页:1 source (单位:千美元)"default" =没有明确的选择, "explicit"),布尔助手(isDefault 编号 : isGranted 编号 : isDenied),选择 updatedAt 编号 : expiresAt,则 optOut() 编号 : optIn() 编号 : reset() 行动。 它与同一标签中的其他组件保持同步(通过 onConsentChange)和其他标签(通过 storage 活动). optOut 通过钩子接受自定义 TTL { declineTtlDays } 选项.
对于非组件背景,则导出相同的原始元素为平面函数: optOut(), (中文(简体) ), optIn(), (中文(简体) ), resetConsent(), (中文(简体) ), getConsentChoice(),以及 onConsentChange()。 。 。 .
已折旧的选入流量
<ConsentBanner />, (中文(简体) ), useConsent(),以及 useConsentState() 为: @deprecated 并计划清除。 旗帜是 永久惯性 它只是在同意时才作出 pending因此,把它挂起来是无害的,但毫无意义. useConsent() 仍作him活isPending 总是 false· ; accept编号 :decline 地图到 optIn编号 :optOut) (中文(简体) ).
从横幅上移去: 删除 <ConsentBanner />,添加一个基于 useCookiePreferences,并在隐私政策中提及跟踪+选择退出。 现有参观者储存的赠款准予停留;自升级后的首次参观起,储存量下降被拒绝停留90天.
出口
createTracking()TrackingProvider范围useTrackingAdPlatformTracking财务报告和审定财务报表getTrackingConfigRuntime; 遗产GoogleAdsTracking- 同意(v2):
useCookiePreferences()+ 键UseCookiePreferencesResult; 独立optIn(), (中文(简体) ),optOut(), (中文(简体) ),resetConsent(), (中文(简体) ),getConsentChoice(), (中文(简体) ),onConsentChange(), (中文(简体) ),getConsentState(), (中文(简体) ),setConsentState() - 无效的同意:
ConsentBanner+ 键ConsentBannerProps, (中文(简体) ),useConsent()+ 键UseConsentResult, (中文(简体) ),useConsentState() - 归属钩:
useTrackingParams(), (中文(简体) ),useGclid() createSalesClient()(异同——公用钥匙写作;密钥读取/CRUD,summary, (中文(简体) ),customers.*, (中文(简体) ),business.config+ 金钱/约会助手(toMinor编号 :fromMinor编号 :formatMoney编号 :formatDateInTz) (中文(简体) ). 也可以在@aranova/tracking-react/sales(包含所有销售/客户/配置类型)——推荐的服务器/服务器无服务器代码导入.- 电话:
parsePhone编号 :toE164编号 :formatPhone编号 :formatPhoneAsTyped编号 :phoneField, (中文(简体) ),usePhoneField, (中文(简体) ),PhoneField(截至2001年12月31日的/phone(中文(简体) ) - 代码:
@aranova/tracking-cli—— 说gen类型服务工会(依赖性) - 事件元数据/配置类型,例如
FormSubmitMetadata, (中文(简体) ),PhoneClickMetadata,以及JsonValue