@cnv-vn/track 0.3.0-beta.15 → 0.3.0-beta.17

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.
@@ -665,12 +665,14 @@ interface PanelShapes {
665
665
  * Lý do `loginWithZalo()` thất bại (`ZaloQrLoginError.reason`). LƯU Ý phạm vi
666
666
  * reject khác nhau:
667
667
  * - `'not-initialized' | 'disabled' | 'consent-required' | 'invalid-project-key'
668
- * | 'project-suspended' | 'network'`: reject ngay lời gọi `loginWithZalo(...)`
668
+ * | 'project-suspended' | 'origin-required' | 'zalo-login-not-configured'
669
+ * | 'zalo-login-unavailable' | 'network'`: reject ngay lời gọi `loginWithZalo(...)`
669
670
  * (trước khi có {@link ZaloQrLoginSession}) — lỗi thiết lập phiên, không có
670
671
  * `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.
672
+ * - `'expired' | 'denied' | 'consent-declined' | 'zalo-permission-denied'
673
+ * | 'underage-self-declared' | 'cancelled' | 'agent-registration-required'
674
+ * | 'requester-not-ready' | 'login-blocked'`: reject {@link ZaloQrLoginSession.result} —
675
+ * xảy ra SAU khi đã có `session` (QR đã hiển thị), khi chờ kết quả quét.
674
676
  *
675
677
  * Bọc `try/catch` quanh CẢ hai điểm `await` (xem ví dụ ở `loginWithZalo` JSDoc)
676
678
  * để không bỏ sót nhóm đầu.
@@ -686,14 +688,57 @@ interface PanelShapes {
686
688
  * 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
689
  * đạ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
690
  * thông báo chung, vẫn cho thử lại.
691
+ *
692
+ * Lỗi tạo phiên theo HTTP status của `POST /zalo-qr/session`: `'zalo-login-not-configured'` (422 —
693
+ * 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),
694
+ * `'zalo-login-unavailable'` (503 — Mini App chưa ACTIVE/chưa xác minh được, tạm thời),
695
+ * `'origin-required'` (403 `origin_required` — request không có header `Origin`, tức không gọi
696
+ * từ trình duyệt; KHÁC `'project-suspended'`, cũng là 403).
697
+ *
698
+ * Khách TỰ từ chối ở Mini App — `denied` kèm lý do từ server: `'consent-declined'` (bấm Huỷ ở
699
+ * màn xin quyền của Mini App CNV dùng chung; KHÁC `'consent-required'`, vốn là đồng ý tracking
700
+ * trên website), `'zalo-permission-denied'` (từ chối popup quyền số điện thoại của Zalo),
701
+ * `'underage-self-declared'` (tự khai chưa đủ 16 tuổi). Không có lý do (Mini App riêng của shop,
702
+ * backend cũ) vẫn là `'denied'`.
703
+ *
704
+ * `'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ơ
705
+ * 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
706
  */
690
- type ZaloQrLoginErrorReason = 'not-initialized' | 'disabled' | 'consent-required' | 'invalid-project-key' | 'project-suspended' | 'network' | 'expired' | 'denied' | 'cancelled' | 'agent-registration-required' | 'login-blocked';
707
+ 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';
708
+ /**
709
+ * Mã QR mở Mini App NÀO: `'SHOP'` = Mini App riêng của shop; `'CNV_SHARED'` = Mini App CNV dùng chung
710
+ * (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,
711
+ * 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").
712
+ */
713
+ type ZaloQrMiniAppKind = 'SHOP' | 'CNV_SHARED';
691
714
  /** Tuỳ chọn cho `loginWithZalo()`. */
692
715
  interface ZaloQrLoginOptions {
693
716
  /** Ưu tiên SSE (mặc định `true`); `false` ⇒ chỉ dùng poll ngay từ đầu. */
694
717
  preferSse?: boolean;
695
718
  /** Khoảng poll fallback (ms). Mặc định 2000. */
696
719
  pollIntervalMs?: number;
720
+ /**
721
+ * 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
722
+ * 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
723
+ * của Mini App).
724
+ *
725
+ * 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
726
+ * — 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ị
727
+ * 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.
728
+ */
729
+ onProgress?: (progress: ZaloQrLoginProgress) => void;
730
+ }
731
+ /** Tiến trình của một phiên QR chưa kết thúc — xem {@link ZaloQrLoginOptions.onProgress}. */
732
+ interface ZaloQrLoginProgress {
733
+ /** `'scanned'`: khách đã quét mã, Mini App đang chờ khách xác nhận trên điện thoại. */
734
+ stage: 'scanned';
735
+ /**
736
+ * 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
737
+ * 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
738
+ * 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
739
+ * cũ vẫn đúng, cứ đếm tiếp.
740
+ */
741
+ expiresInSeconds?: number;
697
742
  }
698
743
  /**
699
744
  * Kết quả khi người dùng quét QR và xác thực Zalo thành công.
@@ -743,12 +788,18 @@ interface ZaloQrLoginResult {
743
788
  */
744
789
  interface ZaloQrLoginSession {
745
790
  sessionToken: string;
746
- /** Deep-link Mini App Zalo — host tự vẽ thành mã QR. */
791
+ /**
792
+ * 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
793
+ * 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ở
794
+ * thẳng app Zalo), QR chỉ còn là phương án quét bằng máy khác.
795
+ */
747
796
  qrPayload: string;
748
797
  expiresInSeconds: number;
798
+ /** Mini App mà mã QR mở — xem {@link ZaloQrMiniAppKind}. Server cũ không gửi ⇒ `'SHOP'`. */
799
+ miniAppKind: ZaloQrMiniAppKind;
749
800
  /**
750
801
  * 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}
802
+ * có `reason` là `'expired' | 'denied' | 'cancelled'`… (xem {@link ZaloQrLoginErrorReason}
752
803
  * để phân biệt với lỗi thiết lập phiên, vốn reject lời gọi `loginWithZalo()` chứ
753
804
  * không phải field này).
754
805
  */
@@ -773,6 +824,64 @@ interface ZaloQrLoginSession {
773
824
  */
774
825
  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
826
 
827
+ /**
828
+ * ============================================================================
829
+ * zaloQrLogin — đăng nhập bằng Zalo qua QR cho website nhúng SDK
830
+ * ============================================================================
831
+ *
832
+ * `loginWithZalo()` là API ĐẦU TIÊN của SDK không theo hợp đồng "never throw,
833
+ * fire-and-forget" của các method khác (track/identify/...) — nó trả Promise
834
+ * thật, và có THỂ REJECT Ở 2 ĐIỂM khác nhau (xem `ZaloQrLoginErrorReason` ở
835
+ * types.ts để biết chi tiết từng reason):
836
+ * 1. Lời gọi `loginWithZalo()` chính nó — lỗi thiết lập phiên (chưa init,
837
+ * dashboard tắt, thiếu consent, lỗi mạng khi tạo phiên).
838
+ * 2. `session.result` — lỗi xảy ra SAU khi đã có phiên/đã hiển thị QR
839
+ * (hết hạn, bị từ chối, bị huỷ).
840
+ * Consumer cần bọc try/catch quanh CẢ HAI điểm await. Vì cần tương tác 2
841
+ * chiều thực sự (đăng ký phiên → chờ QR được quét), hàm này KHÔNG đi qua
842
+ * queue protocol `window.cnvQ` — chỉ dùng được qua ES-module import.
843
+ *
844
+ * SDK không tự vẽ QR: trả `qrPayload` (deep-link Mini App) để host page tự
845
+ * render `<img>`/canvas, đúng vai trò "tracker" chứ không phải "auth widget"
846
+ * (xem cách `demo/index.html` tự vẽ modal login cho `identify()`).
847
+ *
848
+ * Kênh nhận kết quả: SSE (`GET {endpoint}/zalo-qr/stream`) làm chính, tự
849
+ * fallback sang poll (`GET {endpoint}/zalo-qr/status`) nếu `EventSource`
850
+ * 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ự
851
+ * phòng" mà `transport.ts` áp dụng (sendBeacon → fetch).
852
+ *
853
+ * Làm mới QR hết hạn: `session.retry()` — tự huỷ phiên hiện tại (an toàn dù đã
854
+ * 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
855
+ * ban đầu, trả về `ZaloQrLoginSession` mới để host render lại QR + await `result`
856
+ * mới. Không cần tự lưu lại `host`/`opts` hay gọi lại `loginWithZalo()` thủ công.
857
+ * ============================================================================
858
+ */
859
+
860
+ /** Phần SDK `loginWithZalo()` cần từ runtime — tách nhỏ để test không phải mock cả module runtime. */
861
+ interface ZaloQrLoginHost {
862
+ readonly projectId: string | null;
863
+ readonly endpoint: string | null;
864
+ readonly zaloQrLoginEnabled: boolean;
865
+ readonly consentGranted: boolean;
866
+ /**
867
+ * Nguồn đưa khách tới lần đầu, ĐÃ ở dạng wire (`utm_*`) — xem `core/attribution.ts`. `null` khi
868
+ * chưa ghi được tín hiệu nào.
869
+ *
870
+ * 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
871
+ * `allowAdTracking` (không gửi click-id khi shop tắt) và chỉ `runtime` biết cấu hình đó. Để
872
+ * zaloQrLogin tự gọi `toAttributionWire` là mở ra một chỗ thứ hai có thể quên mất cái gác.
873
+ */
874
+ getAttribution?(): ContextFirstTouch | null;
875
+ /** anonymousId (`_cnv_cid`) — để server nối phiên QR này với hành vi ẩn danh trước đó. */
876
+ getAnonymousId?(): string;
877
+ getSessionId?(): string;
878
+ }
879
+ /** Lỗi nghiệp vụ của `loginWithZalo()` — xem {@link ZaloQrLoginErrorReason} cho phạm vi reject của từng reason. */
880
+ declare class ZaloQrLoginError extends Error {
881
+ readonly reason: ZaloQrLoginErrorReason;
882
+ constructor(reason: ZaloQrLoginErrorReason);
883
+ }
884
+
776
885
  /**
777
886
  * ============================================================================
778
887
  * queue — Queue Processor (Sub-task 3.5)
@@ -872,49 +981,15 @@ interface Runtime extends Dispatcher {
872
981
  isZaloQrLoginEnabled(): boolean;
873
982
  /** `true` khi cả `analytics` và `marketing` đều được đồng ý — ngưỡng `loginWithZalo()` yêu cầu vì thu thập SĐT. */
874
983
  readonly consentGranted: boolean;
984
+ /**
985
+ * Host cho `loginWithZalo()` (nội bộ) — MỘT chỗ dựng cho cả Panel lẫn wrapper public ở `index.ts`, kèm
986
+ * đủ 3 getter nguồn khách + định danh ẩn danh.
987
+ */
988
+ zaloQrLoginHost(): ZaloQrLoginHost;
875
989
  /** Tear down timers + listeners. Test-only in production. */
876
990
  destroy(): void;
877
991
  }
878
992
 
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
993
  /**
919
994
  * ============================================================================
920
995
  * cnv-track.js — public entry point
@@ -986,4 +1061,4 @@ declare const cnvTrack: Runtime & {
986
1061
  };
987
1062
 
988
1063
  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 };
1064
+ 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.15",
3
+ "version": "0.3.0-beta.17",
4
4
  "description": "CNV Tracking SDK — lightweight, async-first web analytics tracker (<30KB gzipped).",
5
5
  "keywords": [
6
6
  "analytics",