npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@chihqiang/ad-tracker

v0.0.5

Published

ad-tracker 是一款轻量的 TypeScript 广告追踪 SDK。可自动解析链接中的广告参数、缓存数据并完成上报,兼容各类主流广告平台,接入简单、扩展性强。

Readme

ad-tracker

ad-tracker 是一款轻量的 TypeScript 广告追踪 SDK。自动解析广告点击链接中的参数并缓存,提供统一的上报接口,兼容各类广告平台,接入简单、扩展性强。

安装

npm install @chihqiang/ad-tracker

CDN

方式一:script 标签(IIFE)

通过 <script> 标签直接引入,全局暴露 window.AdTracker(命名空间,包含所有导出)。

<!-- unpkg -->
<script src="https://unpkg.com/@chihqiang/ad-tracker@latest/dist/index.global.js"></script>

<!-- jsdelivr -->
<script src="https://cdn.jsdelivr.net/npm/@chihqiang/ad-tracker@latest/dist/index.global.js"></script>

<script>
  const { AdTracker: AdTrackerClass, TencentPlatform } = AdTracker

  const tracker = new AdTrackerClass({
    handler: async (platform, payload) => {
      const res = await fetch('https://your-server.com/api/ad/report', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ platform, ...payload }),
      })
      return res.json()
    },
  })

  tracker.use(new TencentPlatform())
  tracker.init()
  window.tracker = tracker
</script>

<!-- 其他页面/模块通过 window.tracker 上报 -->
<script>
  window.tracker.report({ action_type: AdTracker.ActionType.REGISTER })
  window.tracker.report({ action_type: AdTracker.ActionType.RESERVATION })
  window.tracker.report({ action_type: AdTracker.ActionType.PURCHASE })
  window.tracker.report({ action_type: AdTracker.ActionType.CLAIM_OFFER })
  window.tracker.report({ action_type: AdTracker.ActionType.VIEW })
</script>

方式二:ESM import(Vue / React 等工程化项目,免安装)

<!-- Vue SFC / main.ts 中直接 import CDN 地址 -->
<script setup>
import { AdTracker, TencentPlatform, ActionType } from 'https://esm.sh/@chihqiang/ad-tracker@latest'

const tracker = new AdTracker({
  handler: async (platform, payload) => {
    const res = await fetch('https://your-server.com/api/ad/report', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ platform, ...payload }),
    })
    return res.json()
  },
})

tracker.use(new TencentPlatform())
tracker.init()

function onRegister() {
  tracker.report({ action_type: ActionType.REGISTER })
}
</script>

也可使用 jsdelivr:

import { AdTracker } from 'https://cdn.jsdelivr.net/npm/@chihqiang/ad-tracker@latest/dist/index.mjs'

快速开始

import { AdTracker, TencentPlatform, ActionType } from '@chihqiang/ad-tracker'

const tracker = new AdTracker({
  handler: async (platform, payload) => {
    const res = await fetch('https://your-server.com/api/ad/report', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ platform, ...payload }),
    })
    return res.json()
  },
})

tracker.use(new TencentPlatform())

// 页面加载时解析 URL 中的广告参数,自动按平台存储
tracker.init()

// 在埋点时机手动上报(action_time 由 SDK 自动填充,无需传入)
await tracker.report({
  action_type: ActionType.REGISTER,
  value: 9900,
})

流程

  1. 用户通过广告链接进入页面,init() 匹配所有已注册平台,缓存参数到 localStorage,key 为 AD_PARAMS_{平台名}(全大写)
  2. 用户在项目中调用 tracker.report() 传入转化事件
  3. SDK 遍历所有已注册平台,逐个检查是否有缓存参数,有则调用 handler 回调,传入 (platform, payload)
  4. 用户在自己的 handler 中决定如何发送数据(fetch / axios / 本地存储等)

API

ActionType(int 枚举)

enum ActionType {
  REGISTER,       // 0 - 注册
  RESERVATION,    // 1 - 表单预约
  PURCHASE,       // 2 - 付费
  CLAIM_OFFER,    // 3 - 领取优惠
  VIEW,           // 4 - 浏览内容
}

AdTracker

import { AdTracker } from '@chihqiang/ad-tracker'

const tracker = new AdTracker({
  handler?: ReportHandler   // 上报回调,接收 (platform, payload)
  storage?: IStorage        // 存储方式,默认 localStorage(浏览器)或内存
  debug?: boolean           // 开启调试日志,默认 false(等同于 LogLevel.DEBUG)
  logger?: Logger           // 自定义日志实例,可控制日志级别
  getUid?: GetUid           // 获取 uid 的回调,传入平台名,返回 Promise<string | number>
})

.use(platform)

注册广告平台适配器。可注册多个。

tracker.use(new TencentPlatform())
tracker.use(new BaiduPlatform())

.init(page?)

解析页面中的广告参数,按平台分别存储。不传参时默认读取 window.location。返回 this 支持链式调用。

tracker.init()
// 或传入 PageLocation
import { toPage } from '@chihqiang/ad-tracker'
tracker.init(toPage('https://example.com/click?click_id=abc&ad=tencent')).report({ action_type: ActionType.REGISTER })

// 小程序环境
tracker.init(toPage('pages/index/index?click_id=abc&ad=tencent'))

.report(event)

上报转化事件。遍历所有已注册平台,逐个检查缓存数据,有则调用 handler

await tracker.report({
  action_type: ActionType.REGISTER,  // 必填
  uid: 'user_123',                    // 自定义参数
})
// action_time 由 SDK 自动填充 Date.now(),无需传入

.dispose()

释放所有平台资源和日志输出。

tracker.dispose()

BasePlatform

所有平台适配器的基类。

子类必须定义三个成员:

| 成员 | 说明 | |------|------| | name | 平台唯一标识(如 'tencent') | | actionMap | ActionType → 平台标准事件字符串的映射 | | defaultMatch() | 判断页面是否属于本平台的匹配规则 |

BasePlatform 内置 match() 方法的优先级:options.match > defaultMatch()。即构造时传入 match 可覆盖子类默认规则。

每个平台实例拥有独立的 Logger,tag 为平台 name(如 "tencent"),日志前缀 【AD-TRACKER.tencent】

PlatformOptions

interface PlatformOptions {
  tag?: string                              // 标记多实例,追加到 storageKey
  match?: (page: PageLocation) => boolean   // 自定义匹配,优先级高于 defaultMatch
}

| 用法 | 说明 | |------|------| | new TencentPlatform() | 默认行为 | | new TencentPlatform({ tag: 'app1' }) | 区分多实例,key 为 AD_PARAMS_TENCENT_APP1 | | new TencentPlatform({ match: fn }) | 覆盖内置匹配规则 | | new BasePlatform({ tag: 'ad', match: fn }) | 无需创建子类,直接使用 |

.buildPayload(event)

组装完整上报参数(无缓存参数时返回 null)。

const payload = await platform.buildPayload({
  action_type: ActionType.REGISTER,
  value: 9900,
})
// payload: { params: {...}, action_type: 'REGISTER', action_time: ..., uid: ..., uuid: ..., options: { tag: 'app1' } }

ReportHandler

handler 接收 (platform, payload) 后自由处理(发送请求、写入队列等)。

const tracker = new AdTracker({
  async handler(platform, payload) {
    // platform: 'tencent'
    // payload: { params, action_type, action_time, uid?, uuid?, options?, ...event }
    const res = await fetch('https://your-server.com/api/ad/report', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ platform, ...payload }),
    })
    return res.json()
  },
})

payload 结构:

{
  "params": {
    "click_id": "24oi6xq2aaakvagnqu7a",
    "account_id": "9471147",
    "click_time": "1586437362"
  },
  "action_type": "REGISTER",
  "action_time": 1717000000,
  "value": 9900,
  "uid": "user_123",
  "uuid": "ae988a05-3cfd-46c2-b640-586a26273a0a",
  "options": {
    "tag": "app1"
  }
}

Logger

日志工具,按级别控制输出。AdTracker 构造时可传入自定义 Logger 实例。

import { Logger, LogLevel } from '@chihqiang/ad-tracker'

// 自定义日志级别
const tracker = new AdTracker({
  logger: new Logger('AdTracker', LogLevel.WARN),  // 仅输出 warn
})

// 或简写为 debug: true(等价于 LogLevel.DEBUG)
const tracker = new AdTracker({ debug: true })

级别枚举(数字越小越详细):

| LogLevel | 值 | 说明 | |----------|----|------| | DEBUG | 0 | 全部输出 | | WARN | 1 | 仅输出警告 | | NONE | 2 | 关闭所有日志 |

const logger = new Logger('MyTag')

logger.debug('查询用户信息', { userId: 123 })  // 对象参数在控制台可展开
logger.warn('接口超时', { url: '/api' })

// 也支持按级别写入
logger.log(LogLevel.DEBUG, '查询用户信息', { userId: 123 })

logger.setLevel(LogLevel.WARN)   // 运行时动态调整级别
logger.dispose()                  // 释放日志(关闭输出)

日志输出格式:

[2026-05-29 12:00:00] 【AD-TRACKER.MyTag】 DEBUG 查询用户信息 {userId: 123}
[2026-05-29 12:00:00] 【AD-TRACKER.MyTag】 WARN 接口超时
  • 时间戳精确到秒
  • 【AD-TRACKER.tag】 标识来源,tag 为传入的标识名
  • 仅对象类型的 data 会展开输出到控制台,字符串/原始值不输出

IStorage

存储接口,浏览器环境默认使用 localStorage(跨页面不丢失),Node/SSR 环境回退到内存存储。

import { MemoryStorage, SessionStorageWrapper, LocalStorageWrapper } from '@chihqiang/ad-tracker'

const tracker = new AdTracker({
  storage: new LocalStorageWrapper(),
})

PageLocation

页面结构化信息,浏览器环境包含完整 URL 字段,小程序环境仅 href / pathname / search / query 有值。

interface PageLocation {
  href: string                       // 完整 URL(浏览器),或 path?query(小程序)
  host?: string                      // 域名,小程序无此字段
  protocol?: string                  // 协议,小程序无此字段
  port?: string                      // 端口,小程序无此字段
  pathname: string                   // 路径
  search: string                     // 查询字符串(含 ?)
  query: Record<string, string>      // 解析后的查询参数
}

toPage

将 href 字符串解析为 PageLocation,同时支持浏览器 URL 和小程序 path?query 格式。

import { toPage } from '@chihqiang/ad-tracker'

// 浏览器 URL → 包含 host / protocol / port
const page = toPage('https://example.com/click?click_id=abc&ad=tencent')
// { href: '...', host: 'example.com', protocol: 'https', port: '', pathname: '/click', search: '?...', query: { click_id: 'abc', ad: 'tencent' } }

// 小程序 path?query → 仅 href / pathname / search / query 有值
const page = toPage('pages/index/index?click_id=abc&ad=tencent')
// { href: '...', pathname: '/pages/index/index', search: '?...', query: { click_id: 'abc', ad: 'tencent' } }

genUuid

UUID v4 生成函数,自实现无外部依赖。

import { genUuid } from '@chihqiang/ad-tracker'

const id = genUuid()  // 'ae988a05-3cfd-46c2-b640-586a26273a0a'

优先使用 crypto.randomUUID(),降级到 crypto.getRandomValues(),兜底 Math.random()

内置平台

TencentPlatform

匹配包含 click_idad=tencent 的腾讯广告点击链接,存储 key 为 AD_PARAMS_TENCENT

actionMap 将 ActionType 映射为腾讯标准事件字符串。可通过 setActionMap() 覆盖或追加映射。

import { TencentPlatform } from '@chihqiang/ad-tracker'
const tencent = new TencentPlatform()
tencent.setActionMap({
  [ActionType.REGISTER]: 'my_register',  // 覆盖
  99: 'custom_event',                     // 追加
})
tracker.use(tencent)

OceanEnginePlatform

匹配包含 clickidad=oceanengine 的巨量引擎广告点击链接,存储 key 为 AD_PARAMS_OCEANENGINE

actionMap 将 ActionType 映射为巨量引擎事件字符串。可通过 setActionMap() 覆盖或追加。

import { OceanEnginePlatform } from '@chihqiang/ad-tracker'
tracker.use(new OceanEnginePlatform())

接入其他广告平台

继承 BasePlatform,必须覆盖 nameactionMapdefaultMatch()。也可通过构造参数 PlatformOptions 覆盖默认行为。

import { BasePlatform, AdTracker, ActionType } from '@chihqiang/ad-tracker'
import type { PageLocation } from '@chihqiang/ad-tracker'

// 方式一:创建子类(推荐,复用性强)
class BaiduPlatform extends BasePlatform {
  readonly name = 'baidu'
  actionMap: Record<number, string | number> = {
    [ActionType.REGISTER]: 'signup',
    [ActionType.PURCHASE]: 'order',
  }
  protected defaultMatch(page: PageLocation): boolean {
    return !!page.query.bd_click_id
  }
}

// 多个实例用 tag 区分 storage
tracker.use(new BaiduPlatform({ tag: 'app1' }))
tracker.use(new BaiduPlatform({ tag: 'app2' }))
const tracker = new AdTracker({ handler: async () => {} })
tracker.use(new BaiduPlatform())
tracker.init()
await tracker.report({ action_type: ActionType.REGISTER, user_id: '123' })

// 方式二:直接传入 match,无需创建子类
tracker.use(new BasePlatform({
  tag: 'my_ad',
  match: (page) => !!page.query.my_click_id,
}))