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

@cnv-vn/track

v0.2.0-alpha.8

Published

CNV Tracking SDK — lightweight, async-first web analytics tracker (<20KB gzipped).

Downloads

410

Readme

cnv-track.js

Lightweight, async-first web analytics tracker — bundle size ≤ 20 KB gzipped.

cnv-track.js là Web JS SDK của hệ thống tracking CNV, có vai trò tương đương Google Analytics / Facebook Pixel: nhúng một dòng <script> vào website khách hàng, tự động thu thập ngữ cảnh và bắn sự kiện tùy chỉnh về Tracking Gateway nội bộ.

Trạng thái hiện tại

| Task | Hạng mục | Trạng thái | | ---- | ----------------------------------------------------- | ----------------------- | | 1 | Setup môi trường & thiết kế snippet | ✅ Hoàn thành | | 2.1 | Identity: Anonymous Client ID (cookie + localStorage) | ✅ Hoàn thành | | 2.2 | Session ID (inactivity 30 phút, cookie + sessionStg) | ✅ Hoàn thành | | 2.3 | Context Collector (page / screen / UTM / click-id) | ✅ Hoàn thành | | 3 | Public API: init, pageview, track, identify | ✅ Hoàn thành | | 3.5 | Queue Processor (drain cnvQ, sync .push proxy) | ✅ Hoàn thành | | 4.1 | Transport: sendBeacon (ưu tiên) | ✅ Hoàn thành | | 4.2 | Transport: Fetch / XHR fallback | ✅ fetch+keepalive | | 4.3 | Content-Type text/plain → bỏ CORS preflight | ✅ Hoàn thành | | 4.4 | Offline retry queue (localStorage + online) | ✅ Hoàn thành | | 5 | QA, performance audit, cross-browser | ✅ 259 test / 5.5KB | | 6 | Build, CDN deploy, Integration guide | ✅ docs-site (30 trang) |

Tham khảo kế hoạch tổng thể.

Endpoint contract

| Endpoint | Khi nào | | -------------------------------------------- | ------------------------------------ | | POST https://cnvcdp.net/api/segment/batch | Mặc định — flush nhiều event / 1 req | | POST https://cnvcdp.net/api/segment/{type} | useBatch: false — 1 event / req |

Schema mirror nguyên com.cnv.tracking.dto.SegmentMessage Java DTO (camelCase Segment Spec). Chi tiết: docs/SCHEMA.md.

CORS (Sub-task 4.3): SDK gửi Content-Type: text/plain (mặc định) — giá trị CORS-safelisted nên không phát OPTIONS preflight. Body vẫn là JSON nguyên vẹn → backend phải parse text/plain thành JSON. Đặt contentType: 'application/json' nếu backend bắt buộc strict (khi đó backend phải allow OPTIONS /segment/* từ origin khách). SDK luôn gửi credentials: 'omit' nên server có thể trả Access-Control-Allow-Origin: *.

Cấu trúc dự án

.
├── src/
│   ├── index.ts              # Public entry — installs queue + exports default
│   ├── types.ts              # Segment Spec wire schema (camelCase)
│   ├── global.d.ts           # window.cnvQ + build-time constants
│   ├── utils/
│   │   ├── uuid.ts           # RFC-4122 v4 generator + validator
│   │   └── storage.ts        # Cookie + LS + sessionStorage helpers
│   └── core/
│       ├── identity.ts       # Anonymous Client ID resolver
│       ├── userIdentity.ts   # Authenticated userId + traits (Sub-task 3.4)
│       ├── session.ts        # Session ID + inactivity timeout
│       ├── context.ts        # Context Collector (page/screen/UTM/library)
│       ├── messageBuilder.ts # SegmentMessage factory (track/page/identify/…)
│       ├── transport.ts      # Batch buffer + beacon/fetch flush → /segment/batch
│       ├── retryQueue.ts     # Offline retry queue (localStorage + online replay)
│       ├── queue.ts          # Queue Processor (drain cnvQ + sync .push proxy)
│       └── runtime.ts        # SDK state holder + Public API dispatcher
├── tests/
│   ├── uuid.test.ts          # Vitest — UUID v4
│   ├── storage.test.ts       # Vitest — cookie + LS + SS helpers
│   ├── identity.test.ts      # Vitest — Client ID lifecycle
│   ├── userIdentity.test.ts  # Vitest — userId / traits lifecycle
│   ├── session.test.ts       # Vitest — Session lifecycle + timeout
│   ├── context.test.ts       # Vitest — page/screen/UTM/library
│   ├── messageBuilder.test.ts# Vitest — SegmentMessage factory
│   ├── transport.test.ts     # Vitest — batch buffer, beacon/fetch, content-type
│   ├── retryQueue.test.ts    # Vitest — offline stash + online replay
│   ├── queue.test.ts         # Vitest — Queue Processor
│   ├── runtime.test.ts       # Vitest — end-to-end Public API
│   └── silentFail.test.ts    # Vitest — crash-proof / silent-fail contract
├── snippet/
│   ├── snippet.html      # Bản có chú thích (onboarding)
│   ├── snippet.min.html  # Bản nén (production)
│   └── README.md         # Cách nhúng + biến thể (GTM, SPA, consent)
├── docs/
│   ├── SCHEMA.md         # Data Schema Payload (human-readable)
│   ├── IDENTITY.md       # Client ID / cookie / LS rules theo RFC 4122 + 6265bis
│   ├── CONTEXT.md        # Context Collector — UTM, click-id, page/screen
│   ├── TRANSPORT.md      # Transport layer — beacon/fetch/retry
│   └── QA.md             # Task 5 — unit test, crash-proof, perf + cross-browser
├── rollup.config.mjs     # Build matrix: IIFE + UMD + ESM + .d.ts
├── vitest.config.ts      # Test config (jsdom env + build-const define)
├── tsconfig.json         # TypeScript strict mode, target ES2015
├── eslint.config.mjs     # Flat config (ESLint v9)
└── package.json          # Scripts + size-limit budgets

Build pipeline (Sub-task 1.1)

Đầu ra cho mỗi lần npm run build:

| File | Format | Mục đích | | ------------------------ | :----: | ----------------------------------------- | | dist/cnv-track.js | IIFE | Debug build, có sourcemap | | dist/cnv-track.min.js | IIFE | CDN target — minified, < 20KB gzipped | | dist/cnv-track.esm.js | ESM | Cho Webpack / Vite / Rollup consumers | | dist/cnv-track.umd.cjs | UMD | Legacy CommonJS / AMD / RequireJS | | dist/types/index.d.ts | .d.ts | Bundled type declarations |

Tại sao multi-format? Người dùng cuối có ba loại: (a) chèn <script> trực tiếp → IIFE, (b) import qua bundler hiện đại → ESM (tree-shakable), (c) Node-style require / RequireJS / NoModule → UMD. Đây là pattern của Segment Analytics, Mixpanel, Amplitude.

Quick start (dev workflow)

npm install                # Cài deps lần đầu
npm run build              # Build full matrix → dist/
npm run build:watch        # Watch mode trong khi viết code
npm run build:analyze      # Sinh dist/stats.html (rollup-plugin-visualizer)
npm run type-check         # tsc --noEmit
npm run lint               # ESLint + typescript-eslint
npm run format             # Prettier
npm run size               # size-limit — fail nếu vượt 20KB
npm run verify             # type-check + lint + format:check + build + size

Decisions & rationale

| Quyết định | Lý do | | -------------------------------- | -------------------------------------------------------------------- | | TypeScript thay vì JS thuần | Bắt bug ở compile time; types là docs sống cho payload schema | | Target ES2015 | Cover 97%+ trình duyệt (caniuse 2026); IE11 đã EOL — bỏ Babel | | Rollup thay vì Webpack | Tree-shaking tốt hơn, format-flexible, chuẩn cho thư viện | | Terser với 3 passes + mangle | Ép kích thước xuống — tối thiểu hóa cost-to-load cho khách hàng | | sideEffects: false | Bật full tree-shaking cho ESM consumers | | size-limit trong CI gate | Bundle bloat sẽ fail PR trước khi merge — kỷ luật cứng | | Snippet protocol versioning | cnvQ.v cho phép SDK mới detect snippet cũ và migrate | | crossOrigin="anonymous" | Bật CORS để bắt lỗi qua window.onerror — cần cho TASK 5 telemetry | | Schema follows Segment Spec | Cho phép swap downstream pipeline (Segment → Snowplow → self-hosted) |

Snippet (Sub-task 1.2)

361 bytes raw (≈ 240B gzipped). Đặt vào <head>:

<script>!function(w,d,s,q,u){if(w[q]&&w[q].__loaded)return;var Q=w[q]=w[q]||[];Q.t=+new Date;Q.v="1.0";Q.push(["init",u]);var f=d.getElementsByTagName(s)[0],j=d.createElement(s);j.async=1;j.defer=1;j.crossOrigin="anonymous";j.src="https://cdn.cnv.vn/cnv-track.min.js";f.parentNode.insertBefore(j,f)}(window,document,"script","cnvQ","YOUR_PROJECT_ID");</script>

Chi tiết: snippet/README.md.

Schema (Sub-task 1.3)

Payload v1.0 theo Segment Spec, snake_case keys, flat properties, ISO-8601 timestamp. Đầy đủ field reference: docs/SCHEMA.md.

Identity & Storage (Sub-task 2.1)

Anonymous Client ID (UUID v4 theo RFC 4122 §4.4), lưu kép trên first-party cookie (RFC 6265bis, SameSite=Lax, TTL 730 ngày) và localStorage để bền vững qua ITP và cookie-banner wipe. Đầy đủ default values, validation rules, browser compat matrix, và tham chiếu pháp lý (GDPR / ePrivacy / CCPA / PDPD): docs/IDENTITY.md.

Public API (Task 3)

Sau khi snippet load cnv-track.min.js, window.cnvTrackwindow.cnvQ.push([...]) đều dispatch về cùng runtime.

// 1. Khởi tạo (snippet đã làm sẵn)
cnvTrack.init('PRJ_XYZ', {
  endpoint: 'https://cnvcdp.net/api', // default
  flushAt: 20, // batch size
  flushInterval: 5000, // ms
  autoPageview: true, // auto fire page on init
  contentType: 'text/plain', // default — skip CORS preflight | 'application/json'
});

// 2. Track sự kiện
cnvTrack.track('AddToCart', { sku: 'A-1', qty: 2 });

// 3. Page / Screen
cnvTrack.pageview(); // mặc định name = document.title
cnvTrack.page('Cart', { ref: 'email' });
cnvTrack.screen('CartScreen', { items: 2 });

// 4. Identify user
cnvTrack.identify('u_42', { email: '[email protected]', name: 'Ngọc' });

// 5. Alias (gộp anonymous → authenticated)
cnvTrack.alias('u_42'); // previousId mặc định = anonymousId

// 6. Group (B2B)
cnvTrack.group('org_acme', { plan: 'enterprise' });

// 7. Consent (GDPR / ePrivacy)
cnvTrack.consent({ marketing: false });

// 8. Reset (sau logout)
cnvTrack.reset();

// 9. Manual flush (trước khi unload)
cnvTrack.flush();

// 10. Hot-patch options
cnvTrack.set({ flushAt: 1 });

Mỗi method tương ứng 1 verb trong QueuedCommandcnvQ.push(['track', 'A'])cnvTrack.track('A') là tương đương 1:1.

Context Collector (Sub-task 2.3)

collectContext() snapshot context.page, context.screen, context.locale, context.timezone, context.user_agent, context.library và — khi URL chứa UTM/click-id — context.campaign. Mọi browser-API access đều bọc try/catch (silent-fail) nên không bao giờ crash host page.

| Sub-collector | Nguồn | | ------------------- | ------------------------------------------------------ | | collectPage() | location.href, document.title, document.referrer | | collectScreen() | screen.{width,height}, devicePixelRatio | | collectCampaign() | URLSearchParams over location.search | | collectContext() | composes the above + navigator.language + IANA tz |

Đầy đủ: nguồn từng field, UTM key map, click-id whitelist, library versioning, browser compat: docs/CONTEXT.md.

Transport (Sub-task 4.1 – 4.4)

Transport buffer event trong bộ nhớ rồi flush theo 4 trigger: đủ flushAt, quá flushInterval, pagehide / visibilitychange→hidden, hoặc flush() thủ công. Mỗi request đi qua hai tầng:

| Thứ tự | Phương thức | Khi nào dùng | | ------ | ---------------------- | -------------------------------------- | | 1 | navigator.sendBeacon | Mặc định — body là Blob text/plain | | 2 | fetch + keepalive | Fallback khi beacon miss |

Beacon "miss" và rơi xuống fetch khi: trình duyệt không có sendBeacon, payload > 64KB (vượt cap của beacon), beacon queue đầy (sendBeacon trả false), hoặc lời gọi ném lỗi (vd. CSP chặn). Vì sao beacon là chính: trình duyệt cam kết gửi nền ngay cả khi tab đóng, không cần setTimeout giữ trang.

Content-Type (Sub-task 4.3). Mặc định text/plain — nằm trong CORS-safelist nên trình duyệt bỏ qua OPTIONS preflight (beacon vì thế gửi gọn lúc unload). Body vẫn là JSON, chỉ header khác → backend phải parse text/plain thành JSON. Đổi contentType: 'application/json' nếu backend bắt buộc strict (khi đó phải allow preflight). Áp dụng cho cả Blob của beacon lẫn header của fetch.

Offline retry queue (Sub-task 4.4). Khi mất mạng — navigator.onLinefalse, hoặc fetch keepalive reject — batch lỗi được stash vào localStorage (key _cnv_rq) thay vì bị bỏ, rồi replay theo FIFO khi có event online hoặc khi SDK boot ở lần tải trang sau. Mỗi event giữ nguyên originalTimestamp, chỉ đóng dấu lại sentAt. Không retry với HTTP error status (4xx/5xx — là response, sẽ loop) hay payload > 64KB (không transport nào gửi được). Hàng đợi cap 100 event, evict batch cũ nhất khi đầy; mọi thao tác storage đều silent-fail.

QA & Performance (Task 5)

| Hạng mục | Kết quả | | ------------------------ | -------------------------------------------------------------------- | | Unit test (Sub-task 5.1) | 259 test / 12 file (Vitest) — coverage 93.85% stmt · 98.78% line | | Crash-proof (5.2) | Silent-fail xuyên suốt + silentFail.test.ts (môi trường thù địch) | | Bundle (5.3) | IIFE 5.52 KB gzipped / 20 KB budget (size-limit CI gate) | | Async load (5.3) | Snippet async+defer+insertBefore → zero-blocking, CLS = 0 | | Cross-browser (5.3) | Chrome / Edge / Firefox / Safari (ITP) — đủ chuỗi fallback |

Mock trong test tuân thủ contract thật (cap 64KB beacon/keepalive được mô hình đúng) nên green chứng minh hành vi thật. Audit còn phát hiện + sửa một lỗi silent-fail thật: throw đồng bộ trong flush bất đồng bộ (timer/pagehide) nay được Transport.send() chặn. Đầy đủ: docs/QA.md.

License

UNLICENSED — internal CNV project. Liên hệ team Data Platform trước khi re-distribute.