@cnv-vn/track 0.3.0-beta.16 → 0.3.0-beta.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -563,6 +563,7 @@ interface LauncherRemoteConfig {
563
563
  buttonShape?: 'SQUARE' | 'SHAVED' | 'ROUNDED' | 'CIRCULAR';
564
564
  placementDesktop?: LauncherPlacement;
565
565
  placementMobile?: LauncherPlacement;
566
+ /** Hex hoặc `linear-gradient(…)` (xem `isValidGradient`). */
566
567
  backgroundColor?: string;
567
568
  textColor?: string;
568
569
  contentLayoutDesktop?: 'ICON_WITH_TEXT' | 'TEXT_ONLY' | 'ICON_ONLY';
@@ -645,10 +646,12 @@ interface PanelContentBlock {
645
646
  description?: string;
646
647
  }
647
648
  interface PanelColors {
649
+ /** Hex hoặc `linear-gradient(…)` (xem `isValidGradient`), như `buttonBg`. */
648
650
  bannerBg?: string;
649
651
  bannerFont?: string;
650
652
  /** Mirror model admin — `core/panel.ts` hiện CHƯA render field này (Panel không có header bar riêng). */
651
653
  headerBarFont?: string;
654
+ /** Hex hoặc `linear-gradient(…)` (xem `isValidGradient`). */
652
655
  buttonBg?: string;
653
656
  buttonFont?: string;
654
657
  linkColor?: string;
@@ -665,12 +668,14 @@ interface PanelShapes {
665
668
  * Lý do `loginWithZalo()` thất bại (`ZaloQrLoginError.reason`). LƯU Ý phạm vi
666
669
  * reject khác nhau:
667
670
  * - `'not-initialized' | 'disabled' | 'consent-required' | 'invalid-project-key'
668
- * | 'project-suspended' | 'network'`: reject ngay lời gọi `loginWithZalo(...)`
671
+ * | 'project-suspended' | 'origin-required' | 'zalo-login-not-configured'
672
+ * | 'zalo-login-unavailable' | 'network'`: reject ngay lời gọi `loginWithZalo(...)`
669
673
  * (trước khi có {@link ZaloQrLoginSession}) — lỗi thiết lập phiên, không có
670
674
  * `session`/`session.result` nào để bắt.
671
- * - `'expired' | 'denied' | 'cancelled' | 'agent-registration-required' | 'login-blocked'`:
672
- * reject {@link ZaloQrLoginSession.result} — xảy ra SAU khi đã có `session` (QR đã hiển
673
- * thị), khi chờ kết quả quét.
675
+ * - `'expired' | 'denied' | 'consent-declined' | 'zalo-permission-denied'
676
+ * | 'underage-self-declared' | 'cancelled' | 'agent-registration-required'
677
+ * | 'requester-not-ready' | 'login-blocked'`: reject {@link ZaloQrLoginSession.result} —
678
+ * xảy ra SAU khi đã có `session` (QR đã hiển thị), khi chờ kết quả quét.
674
679
  *
675
680
  * Bọc `try/catch` quanh CẢ hai điểm `await` (xem ví dụ ở `loginWithZalo` JSDoc)
676
681
  * để không bỏ sót nhóm đầu.
@@ -686,14 +691,57 @@ interface PanelShapes {
686
691
  * làm gì sai và bấm "Thử lại" cũng vô nghĩa: việc cần làm là mở Mini App của shop để đăng ký
687
692
  * đại lý trước. `'login-blocked'` là ca shop chặn vì lý do khác (backend gửi mã lạ) — dùng câu
688
693
  * thông báo chung, vẫn cho thử lại.
694
+ *
695
+ * Lỗi tạo phiên theo HTTP status của `POST /zalo-qr/session`: `'zalo-login-not-configured'` (422 —
696
+ * project chưa cấu hình Mini App nhận QR, và cũng không được dùng Mini App CNV dùng chung),
697
+ * `'zalo-login-unavailable'` (503 — Mini App chưa ACTIVE/chưa xác minh được, tạm thời),
698
+ * `'origin-required'` (403 `origin_required` — request không có header `Origin`, tức không gọi
699
+ * từ trình duyệt; KHÁC `'project-suspended'`, cũng là 403).
700
+ *
701
+ * Khách TỰ từ chối ở Mini App — `denied` kèm lý do từ server: `'consent-declined'` (bấm Huỷ ở
702
+ * màn xin quyền của Mini App CNV dùng chung; KHÁC `'consent-required'`, vốn là đồng ý tracking
703
+ * trên website), `'zalo-permission-denied'` (từ chối popup quyền số điện thoại của Zalo),
704
+ * `'underage-self-declared'` (tự khai chưa đủ 16 tuổi). Không có lý do (Mini App riêng của shop,
705
+ * backend cũ) vẫn là `'denied'`.
706
+ *
707
+ * `'requester-not-ready'`: shop CHƯA sẵn sàng nhận xác thực qua Mini App CNV dùng chung (hồ sơ
708
+ * chưa được duyệt, website chưa khai báo…) — việc của shop, khách thử lại ngay cũng vậy.
689
709
  */
690
- type ZaloQrLoginErrorReason = 'not-initialized' | 'disabled' | 'consent-required' | 'invalid-project-key' | 'project-suspended' | 'network' | 'expired' | 'denied' | 'cancelled' | 'agent-registration-required' | 'login-blocked';
710
+ type ZaloQrLoginErrorReason = 'not-initialized' | 'disabled' | 'consent-required' | 'invalid-project-key' | 'project-suspended' | 'origin-required' | 'zalo-login-not-configured' | 'zalo-login-unavailable' | 'network' | 'expired' | 'denied' | 'consent-declined' | 'zalo-permission-denied' | 'underage-self-declared' | 'cancelled' | 'agent-registration-required' | 'requester-not-ready' | 'login-blocked';
711
+ /**
712
+ * Mã QR mở Mini App NÀO: `'SHOP'` = Mini App riêng của shop; `'CNV_SHARED'` = Mini App CNV dùng chung
713
+ * (shop không có Mini App riêng), nơi khách thấy màn xin quyền rồi mới chia sẻ thông tin. Server chọn,
714
+ * SDK chỉ đọc — dùng để nói đúng câu với khách (vd "liên hệ shop" thay vì "mở Mini App của shop").
715
+ */
716
+ type ZaloQrMiniAppKind = 'SHOP' | 'CNV_SHARED';
691
717
  /** Tuỳ chọn cho `loginWithZalo()`. */
692
718
  interface ZaloQrLoginOptions {
693
719
  /** Ưu tiên SSE (mặc định `true`); `false` ⇒ chỉ dùng poll ngay từ đầu. */
694
720
  preferSse?: boolean;
695
721
  /** Khoảng poll fallback (ms). Mặc định 2000. */
696
722
  pollIntervalMs?: number;
723
+ /**
724
+ * Báo tiến trình của phiên TRƯỚC khi có kết cục — để host đổi màn QR khi khách đã quét mã, thay
725
+ * vì để họ nhìn một mã QR đứng im trong lúc bước tiếp theo nằm trên điện thoại (màn xin quyền
726
+ * của Mini App).
727
+ *
728
+ * Gọi tối đa MỘT lần mỗi phiên (server báo lại "đã quét" ở mỗi nhịp poll và mỗi lần SSE nối lại
729
+ * — SDK lọc hộ). Không phải kết cục: `result` vẫn chờ tiếp như thường. Lỗi ném ra từ callback bị
730
+ * nuốt, không làm hỏng phiên. Server cũ không báo "đã quét" ⇒ không bao giờ được gọi.
731
+ */
732
+ onProgress?: (progress: ZaloQrLoginProgress) => void;
733
+ }
734
+ /** Tiến trình của một phiên QR chưa kết thúc — xem {@link ZaloQrLoginOptions.onProgress}. */
735
+ interface ZaloQrLoginProgress {
736
+ /** `'scanned'`: khách đã quét mã, Mini App đang chờ khách xác nhận trên điện thoại. */
737
+ stage: 'scanned';
738
+ /**
739
+ * Số giây phiên còn sống theo server lúc báo. Server gia hạn phiên khi khách quét (đọc màn xin
740
+ * quyền cần thời gian); SDK đã tự nới hạn chờ của `result` theo đó (chỉ nới, không rút) — host
741
+ * chỉ cần đếm ngược lại từ số này. Thiếu (server không đọc được hạn, hoặc số không hợp lệ) ⇒ hạn
742
+ * cũ vẫn đúng, cứ đếm tiếp.
743
+ */
744
+ expiresInSeconds?: number;
697
745
  }
698
746
  /**
699
747
  * Kết quả khi người dùng quét QR và xác thực Zalo thành công.
@@ -743,12 +791,18 @@ interface ZaloQrLoginResult {
743
791
  */
744
792
  interface ZaloQrLoginSession {
745
793
  sessionToken: string;
746
- /** Deep-link Mini App Zalo — host tự vẽ thành mã QR. */
794
+ /**
795
+ * Deep-link Mini App Zalo — host tự vẽ thành mã QR. Trên ĐIỆN THOẠI khách không quét được màn hình
796
+ * của chính mình: dùng chuỗi này làm `href` của một nút "Mở Zalo" (link `https://zalo.me/...` mở
797
+ * thẳng app Zalo), QR chỉ còn là phương án quét bằng máy khác.
798
+ */
747
799
  qrPayload: string;
748
800
  expiresInSeconds: number;
801
+ /** Mini App mà mã QR mở — xem {@link ZaloQrMiniAppKind}. Server cũ không gửi ⇒ `'SHOP'`. */
802
+ miniAppKind: ZaloQrMiniAppKind;
749
803
  /**
750
804
  * Resolve khi quét xong & backend xác nhận; reject với {@link ZaloQrLoginError}
751
- * có `reason` là `'expired' | 'denied' | 'cancelled'` (xem {@link ZaloQrLoginErrorReason}
805
+ * có `reason` là `'expired' | 'denied' | 'cancelled'`… (xem {@link ZaloQrLoginErrorReason}
752
806
  * để phân biệt với lỗi thiết lập phiên, vốn reject lời gọi `loginWithZalo()` chứ
753
807
  * không phải field này).
754
808
  */
@@ -773,6 +827,64 @@ interface ZaloQrLoginSession {
773
827
  */
774
828
  type QueuedCommand = readonly ['init', string, SDKOptions?] | readonly ['pageview', string?, Record<string, JSONValue>?] | readonly ['page', string?, Record<string, JSONValue>?] | readonly ['screen', string?, Record<string, JSONValue>?] | readonly ['track', string, Record<string, JSONValue>?] | readonly ['identify', string, UserTraits?] | readonly ['alias', string, string?] | readonly ['group', string, Record<string, JSONValue>?] | readonly ['reset'] | readonly ['flush'] | readonly ['consent', Partial<ContextConsent>] | readonly ['set', Partial<SDKOptions>];
775
829
 
830
+ /**
831
+ * ============================================================================
832
+ * zaloQrLogin — đăng nhập bằng Zalo qua QR cho website nhúng SDK
833
+ * ============================================================================
834
+ *
835
+ * `loginWithZalo()` là API ĐẦU TIÊN của SDK không theo hợp đồng "never throw,
836
+ * fire-and-forget" của các method khác (track/identify/...) — nó trả Promise
837
+ * thật, và có THỂ REJECT Ở 2 ĐIỂM khác nhau (xem `ZaloQrLoginErrorReason` ở
838
+ * types.ts để biết chi tiết từng reason):
839
+ * 1. Lời gọi `loginWithZalo()` chính nó — lỗi thiết lập phiên (chưa init,
840
+ * dashboard tắt, thiếu consent, lỗi mạng khi tạo phiên).
841
+ * 2. `session.result` — lỗi xảy ra SAU khi đã có phiên/đã hiển thị QR
842
+ * (hết hạn, bị từ chối, bị huỷ).
843
+ * Consumer cần bọc try/catch quanh CẢ HAI điểm await. Vì cần tương tác 2
844
+ * chiều thực sự (đăng ký phiên → chờ QR được quét), hàm này KHÔNG đi qua
845
+ * queue protocol `window.cnvQ` — chỉ dùng được qua ES-module import.
846
+ *
847
+ * SDK không tự vẽ QR: trả `qrPayload` (deep-link Mini App) để host page tự
848
+ * render `<img>`/canvas, đúng vai trò "tracker" chứ không phải "auth widget"
849
+ * (xem cách `demo/index.html` tự vẽ modal login cho `identify()`).
850
+ *
851
+ * Kênh nhận kết quả: SSE (`GET {endpoint}/zalo-qr/stream`) làm chính, tự
852
+ * fallback sang poll (`GET {endpoint}/zalo-qr/status`) nếu `EventSource`
853
+ * không tồn tại hoặc báo lỗi kéo dài — cùng triết lý "luôn có phương án dự
854
+ * phòng" mà `transport.ts` áp dụng (sendBeacon → fetch).
855
+ *
856
+ * Làm mới QR hết hạn: `session.retry()` — tự huỷ phiên hiện tại (an toàn dù đã
857
+ * hết hạn/bị từ chối/thành công từ trước) rồi tạo phiên MỚI với cùng host/options
858
+ * ban đầu, trả về `ZaloQrLoginSession` mới để host render lại QR + await `result`
859
+ * mới. Không cần tự lưu lại `host`/`opts` hay gọi lại `loginWithZalo()` thủ công.
860
+ * ============================================================================
861
+ */
862
+
863
+ /** Phần SDK `loginWithZalo()` cần từ runtime — tách nhỏ để test không phải mock cả module runtime. */
864
+ interface ZaloQrLoginHost {
865
+ readonly projectId: string | null;
866
+ readonly endpoint: string | null;
867
+ readonly zaloQrLoginEnabled: boolean;
868
+ readonly consentGranted: boolean;
869
+ /**
870
+ * Nguồn đưa khách tới lần đầu, ĐÃ ở dạng wire (`utm_*`) — xem `core/attribution.ts`. `null` khi
871
+ * chưa ghi được tín hiệu nào.
872
+ *
873
+ * Host trả về bản đã ánh xạ, KHÔNG phải `FirstTouch` thô, là cố ý: việc ánh xạ còn phải gác
874
+ * `allowAdTracking` (không gửi click-id khi shop tắt) và chỉ `runtime` biết cấu hình đó. Để
875
+ * zaloQrLogin tự gọi `toAttributionWire` là mở ra một chỗ thứ hai có thể quên mất cái gác.
876
+ */
877
+ getAttribution?(): ContextFirstTouch | null;
878
+ /** anonymousId (`_cnv_cid`) — để server nối phiên QR này với hành vi ẩn danh trước đó. */
879
+ getAnonymousId?(): string;
880
+ getSessionId?(): string;
881
+ }
882
+ /** Lỗi nghiệp vụ của `loginWithZalo()` — xem {@link ZaloQrLoginErrorReason} cho phạm vi reject của từng reason. */
883
+ declare class ZaloQrLoginError extends Error {
884
+ readonly reason: ZaloQrLoginErrorReason;
885
+ constructor(reason: ZaloQrLoginErrorReason);
886
+ }
887
+
776
888
  /**
777
889
  * ============================================================================
778
890
  * queue — Queue Processor (Sub-task 3.5)
@@ -872,49 +984,15 @@ interface Runtime extends Dispatcher {
872
984
  isZaloQrLoginEnabled(): boolean;
873
985
  /** `true` khi cả `analytics` và `marketing` đều được đồng ý — ngưỡng `loginWithZalo()` yêu cầu vì thu thập SĐT. */
874
986
  readonly consentGranted: boolean;
987
+ /**
988
+ * Host cho `loginWithZalo()` (nội bộ) — MỘT chỗ dựng cho cả Panel lẫn wrapper public ở `index.ts`, kèm
989
+ * đủ 3 getter nguồn khách + định danh ẩn danh.
990
+ */
991
+ zaloQrLoginHost(): ZaloQrLoginHost;
875
992
  /** Tear down timers + listeners. Test-only in production. */
876
993
  destroy(): void;
877
994
  }
878
995
 
879
- /**
880
- * ============================================================================
881
- * zaloQrLogin — đăng nhập bằng Zalo qua QR cho website nhúng SDK
882
- * ============================================================================
883
- *
884
- * `loginWithZalo()` là API ĐẦU TIÊN của SDK không theo hợp đồng "never throw,
885
- * fire-and-forget" của các method khác (track/identify/...) — nó trả Promise
886
- * thật, và có THỂ REJECT Ở 2 ĐIỂM khác nhau (xem `ZaloQrLoginErrorReason` ở
887
- * types.ts để biết chi tiết từng reason):
888
- * 1. Lời gọi `loginWithZalo()` chính nó — lỗi thiết lập phiên (chưa init,
889
- * dashboard tắt, thiếu consent, lỗi mạng khi tạo phiên).
890
- * 2. `session.result` — lỗi xảy ra SAU khi đã có phiên/đã hiển thị QR
891
- * (hết hạn, bị từ chối, bị huỷ).
892
- * Consumer cần bọc try/catch quanh CẢ HAI điểm await. Vì cần tương tác 2
893
- * chiều thực sự (đăng ký phiên → chờ QR được quét), hàm này KHÔNG đi qua
894
- * queue protocol `window.cnvQ` — chỉ dùng được qua ES-module import.
895
- *
896
- * SDK không tự vẽ QR: trả `qrPayload` (deep-link Mini App) để host page tự
897
- * render `<img>`/canvas, đúng vai trò "tracker" chứ không phải "auth widget"
898
- * (xem cách `demo/index.html` tự vẽ modal login cho `identify()`).
899
- *
900
- * Kênh nhận kết quả: SSE (`GET {endpoint}/zalo-qr/stream`) làm chính, tự
901
- * fallback sang poll (`GET {endpoint}/zalo-qr/status`) nếu `EventSource`
902
- * không tồn tại hoặc báo lỗi kéo dài — cùng triết lý "luôn có phương án dự
903
- * phòng" mà `transport.ts` áp dụng (sendBeacon → fetch).
904
- *
905
- * Làm mới QR hết hạn: `session.retry()` — tự huỷ phiên hiện tại (an toàn dù đã
906
- * hết hạn/bị từ chối/thành công từ trước) rồi tạo phiên MỚI với cùng host/options
907
- * ban đầu, trả về `ZaloQrLoginSession` mới để host render lại QR + await `result`
908
- * mới. Không cần tự lưu lại `host`/`opts` hay gọi lại `loginWithZalo()` thủ công.
909
- * ============================================================================
910
- */
911
-
912
- /** Lỗi nghiệp vụ của `loginWithZalo()` — xem {@link ZaloQrLoginErrorReason} cho phạm vi reject của từng reason. */
913
- declare class ZaloQrLoginError extends Error {
914
- readonly reason: ZaloQrLoginErrorReason;
915
- constructor(reason: ZaloQrLoginErrorReason);
916
- }
917
-
918
996
  /**
919
997
  * ============================================================================
920
998
  * cnv-track.js — public entry point
@@ -986,4 +1064,4 @@ declare const cnvTrack: Runtime & {
986
1064
  };
987
1065
 
988
1066
  export { NAME, SCHEMA_VERSION, VERSION, ZaloQrLoginError, cnvTrack as default, loginWithZalo };
989
- export type { AutotrackOptions, ButtonTrackingOptions, CaptureSpec, ClickEventSpec, ClickMatch, ClickRule, ContextCampaign, ContextConsent, ContextLibrary, ContextPage, ContextScreen, DataAttrMatch, DestinationMatch, JSONValue, MessageType, PayloadContext, QueuedCommand, RemoteConfigEnvelope, RemoteConfigOptions, SDKOptions, SegmentBatchRequest, SegmentMessage, TextMatch, ThrottleSpec, UrlMatch, UserTraits, ZaloQrLoginErrorReason, ZaloQrLoginOptions, ZaloQrLoginResult, ZaloQrLoginSession };
1067
+ export type { AutotrackOptions, ButtonTrackingOptions, CaptureSpec, ClickEventSpec, ClickMatch, ClickRule, ContextCampaign, ContextConsent, ContextLibrary, ContextPage, ContextScreen, DataAttrMatch, DestinationMatch, JSONValue, MessageType, PayloadContext, QueuedCommand, RemoteConfigEnvelope, RemoteConfigOptions, SDKOptions, SegmentBatchRequest, SegmentMessage, TextMatch, ThrottleSpec, UrlMatch, UserTraits, ZaloQrLoginErrorReason, ZaloQrLoginOptions, ZaloQrLoginProgress, ZaloQrLoginResult, ZaloQrLoginSession, ZaloQrMiniAppKind };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cnv-vn/track",
3
- "version": "0.3.0-beta.16",
3
+ "version": "0.3.0-beta.18",
4
4
  "description": "CNV Tracking SDK — lightweight, async-first web analytics tracker (<30KB gzipped).",
5
5
  "keywords": [
6
6
  "analytics",
@@ -97,13 +97,13 @@
97
97
  {
98
98
  "name": "IIFE (minified + gzipped)",
99
99
  "path": "dist/cnv-track.min.js",
100
- "limit": "50 KB",
100
+ "limit": "60 KB",
101
101
  "gzip": true
102
102
  },
103
103
  {
104
104
  "name": "ESM tree-shaken (gzipped)",
105
105
  "path": "dist/cnv-track.esm.js",
106
- "limit": "50 KB",
106
+ "limit": "60 KB",
107
107
  "gzip": true
108
108
  }
109
109
  ],