@clipto/traffic-source
v0.3.0
Published
Shared traffic-source attribution format for Clipto sites and webhooks
Readme
@clipto/traffic-source
用于生成与解析流量来源的 TypeScript 包,供不同站点及服务共享同一数据格式。提供来源 ID、URL 渠道识别、渠道历史和复合来源的构建与解析;不依赖站点框架、Cookie、支付 SDK 或上报协议。由调用方决定何时采集、存储和上报来源。
安装
pnpm add @clipto/traffic-source快速开始
import {
buildSourceId,
buildTrafficSource,
parseTrafficSourceWithFallback,
classifyTrafficChannel,
} from "@clipto/traffic-source";
const firstTouch = buildSourceId({
payment: "stripe",
product: "web",
businessType: "trans",
channelType: "seo",
});
const trafficSource = buildTrafficSource({
firstTouch,
checkout: "stripe-web-trans-semgoogle-meeting",
history: "seo_semgoogle",
});
const result = parseTrafficSourceWithFallback(trafficSource);
const checkout = result.slots.checkout;
if (checkout.source) {
const classification = classifyTrafficChannel(checkout.source.channelType);
console.log(classification.category);
}trafficSource 的值为 stripe-web-trans-seo/stripe-web-trans-semgoogle-meeting//seo_semgoogle。四槽依次表示首次落地、本次下单、套餐变更和渠道历史集合;新购时第三槽为空正常,第四槽不是完整访问时间线。单条来源 ID 字符串格式仍为 payment-product-business-channel[-scene],对象字段为 payment/product/businessType/channelType/creative?。
来源 ID
| 函数 | 参数 | 返回及用途 |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| buildSourceId(info, onInvalid?) | info 包含必填的 payment、product、businessType、channelType 和可选 creative;回调接收新字段名数组 | 转小写、去首尾空格后生成来源 ID;无效时返回 "",可通过回调获知无效字段。前四段不能包含 - 或 /,creative 可包含 -,不能包含 /。 |
| parseSourceId(raw) | 单条来源 ID 字符串 | 严格返回 { payment, product, businessType, channelType, creative? };不能解析时返回 null。creative 中多余的连字符原样保留。 |
payment、product 和 businessType 由接入方定义;SDK 不限定支付平台、产品和业务值。第五段 creative 只是既有字段改名,历史值可能来自页面场景或 adid,不保证是广告素材 ID。
URL 与渠道
| 函数或常量 | 参数 | 返回及用途 |
| ------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| getChannelFromUrl(targetUrl) | 完整 URL 字符串或 URL 对象 | 只读取当前 URL 的参数,按内置优先级返回渠道编码;无匹配时返回 "seo",不读取 Cookie。 |
| generateSourceIdFromUrl(targetUrl, options) | 完整 URL 字符串;options 必须提供 payment、product、resolveBusinessType(pathname),可提供 resolveCreative(pathname, params)、channelType、onInvalid | 使用 URL 渠道规则及调用方提供的业务/场景映射生成来源 ID。传入 channelType 时跳过默认渠道识别;字段无效时返回 ""。 |
| generateChannelHistory(targetUrl, trafficHistory, trafficSource, newTrafficSource) | 当前 URL、已有历史字符串、首次来源 ID、本次来源 ID | 返回下划线分隔的渠道历史;优先选本次、首次或 URL 中的 SEM,将选中渠道去重后放在末尾。它不是完整访问时间线。 |
| isPaidChannel(channel?) | 渠道编码 | 返回布尔值;现有规则把 SEM、kol 前缀、affiliate、ambassador 视为付费,其他值返回 false。此旧判断不等于“已知渠道分类”。 |
| TrafficChannel、SEM_CHANNELS | 无 | 现有渠道编码常量;新渠道可能未收录,请勿将它们当作封闭的产品枚举。 |
import {
generateSourceIdFromUrl,
getChannelFromUrl,
} from "@clipto/traffic-source";
const url = "https://example.com/meeting?gclid=click&adid=730736096338";
getChannelFromUrl(url);
generateSourceIdFromUrl(url, {
payment: "stripe",
product: "web",
resolveBusinessType: (pathname) =>
pathname.includes("meeting") ? "trans" : "home",
resolveCreative: (_pathname, params) => params.get("adid") ?? "",
});上述调用分别返回 "semgoogle" 和 "stripe-web-trans-semgoogle-730736096338"。URL 渠道识别覆盖指定 banner、KOL、referral、SEM 来源/点击参数、ambassador、affiliate,最后回退到 seo;URL 路径如何映射到业务及场景仍由接入方决定。
复合来源
| 函数 | 参数 | 返回及用途 |
| -------------------------------------------------------- | ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| buildTrafficSource(parts) | firstTouch、checkout 必填;planChange、history 可选 | 按 firstTouch/checkout/planChange/history 生成四槽字符串,保留空槽。 |
| parseTrafficSource(raw) | 复合来源字符串 | 返回原文、四槽原始值、sources(前三槽的严格解析对象或 null)、status(complete/legacy/invalid)和 sourceStatus(valid/partial/invalid)。不抛解析异常。 |
| parseTrafficSourceWithFallback(raw, fallbackCheckout?) | 复合来源字符串、可选的独立下单来源 ID | 保留 parseTrafficSource 的全部原始字段,另加 slots.firstTouch/checkout/planChange:每槽有 raw、source、status、origin。仅原第二槽为空时采用独立 ID,不修改原复合字符串或顶层 sources。 |
| updateTrafficSource(raw, planChange, history) | 旧复合串、新第三槽来源 ID、新第四槽渠道历史 | 更新第三、第四槽;旧串不足四槽时补空,不校验或修复已有内容。 |
每个 slots.*.status 为 "valid" | "missing" | "invalid",origin 为 "composite" | "fallback" | "none"。例如历史数据只有第一槽:
import { parseTrafficSourceWithFallback } from "@clipto/traffic-source";
const result = parseTrafficSourceWithFallback(
"stripe-web-home-seo",
"stripe-web-trans-semgoogle-meeting",
);
result.status;
result.checkout;
result.slots.checkout;这三个表达式依次得到 "legacy"、"" 和 { raw: "stripe-web-trans-semgoogle-meeting", source: { payment: "stripe", product: "web", businessType: "trans", channelType: "semgoogle", creative: "meeting" }, status: "valid", origin: "fallback" }。若第二槽有非空但非法的值,不会回退;第一槽和第三槽也不会用别的槽冒充。status 只表示槽位结构,不保证每个 sources.* 有效;消费某槽请检查其 sources.* !== null 或 slots.*.status === "valid"。
公共分类
| 函数 | 参数 | 返回及用途 |
| ------------------------------------ | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| classifyTrafficChannel(channel) | 单个渠道编码,例如 "semgoogle" | 返回 { channel, category, name }。已知 SEM/KOL 等编码映射为渠道类别和标准化名称;未知或动态值保留原值,返回 category: "unknown"、name: null。 |
| classifyPlanChangeChannel(channel) | 第三槽已解析来源的 channelType 值,不是完整来源 ID | 返回 { channel, category },类别为 upgrade_reason、traffic_channel 或 unknown。五个已知升级标签是 3file、30min、downloader、starter、free;不限制第三槽只使用它们。 |
import {
classifyPlanChangeChannel,
classifyTrafficChannel,
} from "@clipto/traffic-source";
classifyTrafficChannel("semgoogle");
classifyTrafficChannel("new-campaign");
classifyPlanChangeChannel("downloader");以上依次返回 { channel: "semgoogle", category: "SEM", name: "Google 付费投放" }、{ channel: "new-campaign", category: "unknown", name: null } 和 { channel: "downloader", category: "upgrade_reason" }。同一个 downloader 在普通渠道语境可能是 banner;升级标签不能反推唯一入口。来源 ID 也不能反推原始 UTM、campaign、adGroup;creative 的历史值可能来自页面场景或 adid,不能据此推断广告素材 ID。
旧数据兼容
| 函数 | 参数 | 返回及用途 |
| -------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------ |
| parseLegacySourceId(raw) | 旧版单条来源字符串 | 仅供旧调用方:空值返回空字段对象,少于四段时抛错,超过五段时忽略多余段;新接入应使用 parseSourceId。 |
| isLegacySourceId(raw) | 单条来源字符串 | 仅按 - 分割后的段数是否为 4~5 段返回布尔值,不代表严格格式有效。 |
更完整的输入/返回样例和渠道优先级见仓库中的 docs/api/traffic-source-contract.md(此目录不随 npm 包发布)。
