vc-cdp-rn-sdk
v0.2.2
Published
CDP SDK for React Native - Customer Data Platform tracking for iOS and Android
Downloads
1,820
Maintainers
Readme
vc-cdp-rn-sdk
CDP SDK cho React Native — tracking events và user profiles cho hệ thống CDP nội bộ VietCredit. Hỗ trợ cả iOS và Android.
Mục lục
- Cài đặt
- Khởi tạo
- Luồng hoạt động
- User / Profile
- Event Tracking
- Notification Inbox Tracking
- Bảo mật
- Tích hợp React Navigation đầy đủ
- API Reference
Cài đặt
npm install vc-cdp-rn-sdkPeer Dependencies
npm install @react-native-async-storage/async-storage
npm install react-native-device-infoiOS
cd ios && pod installAndroid
Không cần thêm cấu hình nếu dùng React Native >= 0.68 (auto-linking).
Khởi tạo
Khởi tạo SDK một lần duy nhất tại entry point của app. SDK dùng AsyncStorage nên init là async.
// src/lib/cdp.ts
import { CdpRnSdk } from 'vc-cdp-rn-sdk';
let _cdp: CdpRnSdk | null = null;
export async function initCdp(): Promise<CdpRnSdk> {
_cdp = await CdpRnSdk.create({
apiKey: 'YOUR_API_KEY',
secretKey: 'YOUR_SECRET_KEY',
source: 'DOP',
serviceName: 'VC_APP',
baseUrl: 'https://ingestlog.vietcredit.com.vn',
debug: __DEV__,
});
return _cdp;
}
export function getCdp(): CdpRnSdk {
if (!_cdp) throw new Error('CDP SDK chưa được khởi tạo. Gọi initCdp() trước.');
return _cdp;
}// App.tsx
import { useEffect } from 'react';
import { initCdp } from './lib/cdp';
export default function App() {
useEffect(() => {
initCdp();
}, []);
// ...
}Lưu ý: Mỗi lần
CdpRnSdk.create()được gọi, SDK tự động tạosession_idmới — tương ứng với 1 lần mở app.
Cấu hình đầy đủ
| Tham số | Kiểu | Mặc định | Mô tả |
|---|---|---|---|
| apiKey | string | — | Bắt buộc. API Key do CDP cấp |
| secretKey | string | — | Bắt buộc. Secret Key để ký HMAC và mã hoá AES |
| source | string | — | Bắt buộc. Kênh tích hợp (khai báo với CDP trước) |
| serviceName | string | — | Bắt buộc. VC_APP | DOP | LOS | LMS |
| baseUrl | string | Staging URL | URL API CDP (không có /v1) |
| isTest | boolean | false | Đánh dấu môi trường test |
| debug | boolean | false | In log ra console |
| batchSize | number | 10 | Số events tối đa trong 1 batch |
| batchInterval | number | 5000 | Thời gian (ms) giữa các lần flush tự động |
| enableEncryption | boolean | true | Mã hoá PII bằng AES-256-CBC |
| notificationApiBaseUrl | string | — | Domain API notification-inbox (khác baseUrl) — dùng cho trackNotificationEvent() |
Môi trường
| Môi trường | baseUrl |
|---|---|
| Staging | https://stg-ingestlog.vietcredit.com.vn |
| Production | https://ingestlog.vietcredit.com.vn |
Luồng hoạt động
App khởi động
└─> CdpRnSdk.create(config) ← async
│ - Load/tạo device_id → AsyncStorage (persist vĩnh viễn)
│ - Load/tạo anonymous_id → AsyncStorage (persist vĩnh viễn)
│ - Tạo session_id mới → AsyncStorage (mỗi lần mở app)
│ - Load profile_id → AsyncStorage (nếu đã login)
│
├─ User CHƯA login
│ └─> cdp.track() / cdp.screen()
│ profile_id = null, gắn anonymous_id + device_id + session_id
│
└─ User login
└─> cdp.identifyUser(userId, traits)
└─> POST /v1/profiles/track → nhận profile_id
└─> cdp.track() gắn profile_id vào mọi eventStorage scope (AsyncStorage):
| | Vòng đời |
|---|---|
| device_id | Vĩnh viễn (đến khi xoá app) |
| anonymous_id | Vĩnh viễn (rotate khi logout) |
| session_id | Tạo mới mỗi lần create() |
| profile_id | Xoá khi destroy_session() |
User / Profile
identifyUser — Đăng nhập
await cdp.identifyUser(userId, traits?, partyType?)| Tham số | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
| userId | string | ✓ | Mã user trong hệ thống nội bộ |
| traits | UserTraits | | Thông tin nhân khẩu học |
| partyType | 'PERSON' \| 'ORG' | | Mặc định 'PERSON' |
// Cá nhân
await cdp.identifyUser('user_001', {
phone: '0901234567',
email: '[email protected]',
full_name: 'Nguyễn Văn A',
gender: 'M',
dob: '1990-01-15',
idcard: '012345678901',
address: '123 Lê Lợi, Q.1, TP.HCM',
occupation: 'Kỹ sư phần mềm',
nationality: 'Vietnamese',
marital_status: 'single',
});
console.log(cdp.getProfileId()); // → "177671850461184000"
// Tổ chức
await cdp.identifyUser('org_001', {
full_name: 'Công ty TNHH ABC',
phone: '0281234567',
email: '[email protected]',
}, 'ORG');Trait Setters — Cập nhật từng thuộc tính
Chainable. Mỗi setter gọi ngay POST /v1/profiles/track (type: update).
cdp
.add_first_name('Văn A')
.add_last_name('Nguyễn')
.add_user_name('Nguyễn Văn A')
.add_email('[email protected]')
.add_mobile('0901234567')
.add_gender('M')
.add_birthday('1990-01-15')
.add_idcard('012345678901')
.add_old_idcard('123456789')
.add_address('123 Lê Lợi, Q.1, TP.HCM')
.add_occupation('Kỹ sư phần mềm')
.add_nationality('Vietnamese')
.add_marital_status('single')
.add_religion('Buddhist')
.add_zalo_id('zalo_user_001')
.add_tiktok_id('tiktok_user_001');| Method | Trường | Ghi chú |
|---|---|---|
| add_first_name(v) | first_name | |
| add_last_name(v) | last_name | |
| add_user_name(v) | full_name | |
| add_email(v) | email | 🔒 PII |
| add_mobile(v) | phone | 🔒 PII |
| add_gender(v) | gender | 'M' | 'F' | 'O' |
| add_birthday(v) | dob | Date hoặc 'yyyy-MM-dd' |
| add_idcard(v) | idcard | 🔒 PII |
| add_old_idcard(v) | old_idcard | 🔒 PII |
| add_address(v) | address | 🔒 PII |
| add_occupation(v) | occupation | |
| add_nationality(v) | nationality | |
| add_marital_status(v) | marital_status | |
| add_religion(v) | religion | 🔒 PII |
| add_zalo_id(v) | zalo_id | |
| add_tiktok_id(v) | tiktok_id | |
| add_user_attribute(name, val) | custom | Bất kỳ trường nào |
| add_user_attributes(obj) | nhiều trường | Batch update |
| setUserAttributes(obj) | nhiều trường | Alias |
updateProfile — Force-update
await cdp.updateProfile({
traits: { kyc_status: 'verified', mps_verified: true },
consents: [{ consent_type: 'marketing_sms', status: true, consent_date: '2025-06-01' }],
});Consents
cdp.setConsents([
{ consent_type: 'marketing_sms', status: true, consent_date: '2025-06-01' },
{ consent_type: 'marketing_email', status: false },
{ consent_type: 'data_sharing', status: true },
]);Scores
cdp.setScores([
{ score_type: 'credit_score', score_value: 720, score_date: '2025-06-01' },
]);Products
cdp.setProducts([
{ product_code: 'LOAN_CONSUMER', product_name: 'Vay tiêu dùng', status: 'active' },
]);Metadata, Campaign, Push Token
cdp.setMetadata({ schema_version: '2.0', referral_code: 'REF123' });
cdp.setCampaign({
utm_source: 'facebook',
utm_medium: 'cpc',
utm_campaign: 'summer_loan_2025',
});
// Push notification token (Expo hoặc FCM)
cdp.setPushToken('ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]');
cdp.setPushToken('fcm_token_abc123...');getUserIdentities
const ids = cdp.getUserIdentities();
// {
// user_id: 'user_001',
// profile_id: '177671850461184000',
// anonymous_id: 'uuid-...',
// device_id: 'uuid-...',
// session_id: 'uuid-...',
// }
cdp.getProfileId();
cdp.getDeviceId();
cdp.getSessionId();
cdp.getUserAttribute('phone');destroy_session — Đăng xuất
await cdp.destroy_session();
// hoặc alias:
await cdp.reset();| | Hành động |
|---|---|
| userId, profileId, userTraits | Xoá |
| consents, scores, products, campaign | Xoá |
| anonymous_id | Tạo mới, lưu AsyncStorage |
| session_id | Tạo mới, lưu AsyncStorage |
| device_id | Giữ nguyên |
Event Tracking
track — Custom event
cdp.track(eventName, properties?)cdp.track('loan_viewed', {
loan_id: 'LOAN_001',
amount: 50_000_000,
term: 24,
type: 'consumer',
});
cdp.track('button_click', { button: 'apply_now', screen: 'LoanDetailScreen' });
cdp.track('form_submitted', { form: 'loan_application', success: true });screen — Màn hình
Track lượt xem màn hình. Gửi event screen_view.
cdp.screen('HomeScreen');
cdp.screen('LoanDetailScreen', { loan_code: 'CONSUMER_24M' });
cdp.screen('ProfileScreen', { tab: 'personal_info' });Tích hợp React Navigation
// App.tsx
import { NavigationContainer, useNavigationContainerRef } from '@react-navigation/native';
import { getCdp } from './lib/cdp';
export default function App() {
const navRef = useNavigationContainerRef();
return (
<NavigationContainer
ref={navRef}
onReady={() => {
const name = navRef.getCurrentRoute()?.name;
if (name) getCdp().screen(name);
}}
onStateChange={() => {
const name = navRef.getCurrentRoute()?.name;
if (name) getCdp().screen(name);
}}
>
{/* Stack / Tab / Drawer navigators */}
</NavigationContainer>
);
}Batch Processing
Events được gom vào queue rồi flush theo điều kiện:
| Điều kiện | Mô tả |
|---|---|
| batchSize đạt ngưỡng | Mặc định 10 events |
| batchInterval timeout | Mặc định 5 000 ms |
| flush() gọi thủ công | Ngay lập tức |
| App vào background | Gọi flush() trước AppState change |
flush — Gửi ngay
await cdp.flush();// Flush khi app vào background
import { AppState } from 'react-native';
AppState.addEventListener('change', async (state) => {
if (state === 'background') {
await cdp.flush();
}
});destroy — Dọn dẹp
await cdp.destroy();Flush queue còn lại và xoá timer. Không xoá user data.
Notification Inbox Tracking
Tích hợp react-native-noti-sdk
(FCM + Notifee) với vc-cdp-rn-sdk để tracking các sự kiện thông báo
(delivered / open / click / dismiss / read) qua notification-inbox API.
API notification-inbox nằm trên domain khác với API ingest event/profile → cấu hình riêng qua
notificationApiBaseUrl(xem Cấu hình đầy đủ).
Cài đặt react-native-noti-sdk
npm install react-native-noti-sdk
npm install @react-native-firebase/app @react-native-firebase/messaging @notifee/react-native @react-native-clipboard/clipboardcd ios && pod installCấu hình Firebase (phía app tích hợp):
- Android: thêm
android/app/google-services.json+ plugincom.google.gms.google-services. - iOS: thêm
GoogleService-Info.plistvào Xcode project, bật capability Push Notifications + Background Modes > Remote notifications.
Đăng ký background handler (bắt buộc) — gọi ở top-level index.js, trước registerComponent:
// index.js
import NotiSdk from 'react-native-noti-sdk';
NotiSdk.registerBackgroundHandler();Cấu hình domain notification-inbox
const cdp = await CdpRnSdk.create({
apiKey: 'YOUR_API_KEY',
secretKey: 'YOUR_SECRET_KEY',
source: 'DOP',
serviceName: 'VC_APP',
baseUrl: 'https://ingestlog.vietcredit.com.vn', // API events/profile
notificationApiBaseUrl: 'https://consolecdp-dev.ovp.vn', // API notification-inbox — domain khác (tạm thời dùng domain dev)
});Nếu không set notificationApiBaseUrl, trackNotificationEvent(),
getNotificationInboxList() và getNotificationInboxDetail() sẽ bỏ qua và log lỗi (không throw).
API react-native-noti-sdk
import NotiSdk from 'react-native-noti-sdk';
await NotiSdk.init({ debug: true }); // xin quyền, tạo channel, lắng nghe
const token = await NotiSdk.getToken(); // FCM token gửi lên server
NotiSdk.onTokenRefresh(t => sendTokenToServer(t)); // token được làm mới
NotiSdk.onMessage(msg => console.log(msg.title, msg.data)); // app đang mở
NotiSdk.onNotificationOpened(msg => navigate(msg.data.screen)); // tap mở app từ background
const initial = await NotiSdk.getInitialNotification(); // mở app từ trạng thái quit
if (initial) navigate(initial.data.screen);
await NotiSdk.displayNotification({ title: 'Xin chào', body: 'Thông báo', data: { screen: 'home' } });| Hàm | Mô tả |
| --- | --- |
| init(config?) | Khởi tạo SDK: xin quyền, đăng ký iOS, tạo channel, lắng nghe. Idempotent. |
| requestPermission() | Hiển thị hộp thoại xin quyền, trả về AuthorizationStatus. |
| hasPermission() | Trạng thái quyền hiện tại (không hiện hộp thoại). |
| getToken() | Trả về FCM token (string) hoặc null nếu lỗi. |
| deleteToken() | Xoá token hiện tại; lần getToken() sau sẽ tạo token mới. |
| onTokenRefresh(listener) | Lắng nghe token mới; trả về unsubscribe. |
| registerBackgroundHandler() | Đăng ký nhận message ở background. Gọi tại index.js. |
| onMessage(handler) | Nhận NotiMessage khi có message; trả về unsubscribe. |
| onNotificationOpened(handler) | Khi tap thông báo mở app (background); trả về unsubscribe. |
| getInitialNotification() | Thông báo đã mở app từ trạng thái quit, hoặc null. |
| displayNotification(options) | Tự hiển thị một local notification qua Notifee. |
| displayCustomNotification(options) | Hiển thị notification UI tuỳ biến (native), có nút hành động. |
| isCustomUiAvailable() | true nếu native module custom UI đang hoạt động. |
NotiSdkConfig
| Trường | Mặc định | Mô tả |
| --- | --- | --- |
| requestPermissionOnInit | true | Tự xin quyền khi init(). |
| debug | false | Bật log [NotiSdk] ra console. |
| defaultChannel | { id: 'default', name: 'Mặc định', importance: 'high' } | Channel Android mặc định. |
| displayInForeground | true | Tự hiện banner khi nhận message lúc app đang mở. |
NotiMessage
interface NotiMessage {
id?: string;
title?: string;
body?: string;
data: Record<string, string>;
}AuthorizationStatus: 'authorized' | 'provisional' | 'denied' | 'notDetermined'
Android 13+: SDK tự xin quyền
POST_NOTIFICATIONSqua Notifee khi gọirequestPermission()/init(). Đảm bảocompileSdkVersion >= 33.
Custom notification UI (react-native-noti-sdk)
await NotiSdk.displayCustomNotification({
title: 'Custom UI',
body: 'Layout tuỳ biến + nút hành động',
imageUrl: 'https://example.com/banner.png',
fullScreen: false, // true: full-screen intent (vd cuộc gọi đến)
data: { screen: 'detail', tracking_id: 'b7e6c1a2-...' },
buttons: [
{ name: 'Gọi điện', type: 'phone', value: '03658700512' },
{ name: 'Đầu tư', type: 'deep_link', value: 'tinvay://notification' },
],
});| type | Hành vi khi nhấn |
| --- | --- |
| phone | Mở trình quay số với value (ACTION_DIAL, không cần quyền gọi). |
| deep_link | Mở URI value. App phải khai báo intent-filter cho scheme đó. |
Android giới hạn tối đa 3 nút. Cả hai nền tảng dùng native module riêng (không phụ thuộc Notifee cho phần custom UI) — tự đăng ký qua autolinking. iOS cần
pod install; hiển thị ảnh yêu cầu quyền thông báo đã cấp; nút share mởUIActivityViewControllerkhi app ở foreground.
Tự động track delivered / open — bindNotificationTracking
bindNotificationTracking() gắn react-native-noti-sdk vào CDP SDK: tự động
gọi trackNotificationEvent() khi nhận message (delivered) và khi user tap
mở app từ thông báo (open), miễn là payload push có field tracking_id
trong data.
import NotiSdk from 'react-native-noti-sdk';
import { getCdp } from './lib/cdp';
async function setupPush() {
await NotiSdk.init({ debug: true });
// onMessage → 'delivered', onNotificationOpened/getInitialNotification → 'open'
const unbind = getCdp().bindNotificationTracking(NotiSdk);
// unbind() khi cleanup nếu cần
}Payload push phải nhúng tracking_id, ví dụ FCM data:
{
"tracking_id": "b7e6c1a2-...",
"deeplink": "app://home"
}Track thủ công click / dismiss / read
react-native-noti-sdk không có callback riêng cho các sự kiện này (nhấn
nút trong displayCustomNotification, dismiss, hoặc đọc trong màn hình
inbox) — gọi trackNotificationEvent() trực tiếp tại nơi xử lý:
// Khi user nhấn nút trong custom notification
cdp.trackNotificationEvent(trackingId, 'click', {
buttonId: 'btn_1',
deeplink: 'app://home',
currentScreen: 'HomeScreen',
});
// Khi user dismiss thông báo
cdp.trackNotificationEvent(trackingId, 'dismiss');
// Khi user đọc thông báo trong màn hình inbox
cdp.trackNotificationEvent(trackingId, 'read', { currentScreen: 'NotificationInboxScreen' });async trackNotificationEvent(
trackingId: string,
eventType: 'delivered' | 'open' | 'click' | 'dismiss' | 'read',
options?: {
userAppId?: string; // mặc định: user_id hiện tại (identifyUser), '' nếu chưa login
userProfileId?: string; // mặc định: profile_id hiện tại, '' nếu chưa có profile_id
deviceId?: string; // mặc định: device_id của SDK
platform?: string; // mặc định: Platform.OS ('android' | 'ios')
buttonId?: string;
deeplink?: string;
currentScreen?: string;
metadata?: unknown[];
}
): Promise<{ code?: number; message?: string } | undefined>Danh sách và chi tiết thông báo
getNotificationInboxList() — danh sách thông báo, phân trang bằng cursor:
const page1 = await cdp.getNotificationInboxList({ filter: 'all', perPage: 20 });
console.log(page1?.data); // NotificationInboxItem[]
console.log(page1?.next_cursor); // dùng cho trang tiếp theo
const page2 = await cdp.getNotificationInboxList({
filter: 'all',
perPage: 20,
cursor: page1?.next_cursor ?? undefined,
});getNotificationInboxDetail() — chi tiết 1 thông báo theo tracking_id:
const detail = await cdp.getNotificationInboxDetail('b7e6c1a2-...');
console.log(detail?.data);userAppId / userProfileId / deviceId tự lấy từ state hiện tại của SDK
(identifyUser() / getDeviceId()) nếu không truyền vào — giống
trackNotificationEvent().
async getNotificationInboxList(options?: {
userAppId?: string; // mặc định: user_id hiện tại, '' nếu chưa login
userProfileId?: string; // mặc định: profile_id hiện tại, '' nếu chưa có
deviceId?: string; // mặc định: device_id của SDK
filter?: string; // mặc định 'all'
perPage?: number; // mặc định 20
cursor?: string; // phân trang — lấy từ next_cursor
}): Promise<{
code?: number;
message?: string;
data?: NotificationInboxItem[];
next_cursor?: string | null;
has_more?: boolean;
} | undefined>
async getNotificationInboxDetail(
trackingId: string,
options?: { userAppId?: string; deviceId?: string }
): Promise<{ code?: number; message?: string; data?: NotificationInboxItem } | undefined>| Method | Endpoint |
|---|---|
| getNotificationInboxList() | GET {notificationApiBaseUrl}/api/v1/notification-inbox?... |
| getNotificationInboxDetail() | GET {notificationApiBaseUrl}/api/v1/notification-inbox/by-tracking-id?tracking_id=... |
| trackNotificationEvent() | POST {notificationApiBaseUrl}/api/v1/notification-inbox/track-by-tracking-id |
Cả 3 endpoint không dùng HMAC signature như API ingest — chỉ header
Content-Type: application/json.
Bảo mật
HMAC-SHA256 Signature
Mọi request đến CDP API đều được ký tự động.
Headers:
X-Api-Key: <apiKey>
X-Source: <source>
X-Timestamp: <unix_timestamp_s>
X-Signatures: <base64_signature>
X-Trace-Id: <uuid>
Content-Type: application/json
isTest: falseCông thức:
signature = Base64( HMAC-SHA256(secretKey, message) )
message = source + "|" + timestamp + "|" + JSON(payload)Lưu ý: RN SDK dùng timestamp tính bằng giây (khác với Web SDK dùng milliseconds).
AES-256-CBC Encryption
Khi enableEncryption: true, các trường PII được mã hoá.
Các trường PII bị mã hoá:
| Trường | Mô tả |
|---|---|
| phone | Số điện thoại |
| email | Email |
| idcard | CMND/CCCD mới |
| old_idcard | CMND cũ |
| address | Địa chỉ |
| religion | Tôn giáo |
| full_name | Họ tên đầy đủ |
| first_name | Tên |
| last_name | Họ |
| gender | Giới tính |
| dob | Ngày sinh |
Công thức:
encrypted = Base64( IV[16 bytes] || AES_CBC_PKCS7( key, IV, plaintext ) )
key = SHA256( secretKey + timestamp )
IV = random 16 bytes (prepend vào ciphertext)
plaintext = JSON.stringify({ chỉ các trường PII })Lưu ý: Không bật trên dev/staging nếu server chưa cấu hình giải mã. Dùng
enableEncryption: false.
Best Practices
- Không log
secretKeyhayapiKeyvào console production. - Bật
debug: falsetrên production build. - Rotate key theo chính sách CDP team.
- Dùng environment variables, không hardcode credentials.
// ✅ Đúng — dùng env vars
import Config from 'react-native-config';
const cdp = await CdpRnSdk.create({
apiKey: Config.CDP_API_KEY,
secretKey: Config.CDP_SECRET_KEY,
source: 'DOP',
serviceName: 'VC_APP',
debug: __DEV__,
});# .env
CDP_API_KEY=YOUR_API_KEY
CDP_SECRET_KEY=YOUR_SECRET_KEY
CDP_BASE_URL=https://stg-ingestlog.vietcredit.com.vn
# .env.production
CDP_API_KEY=<production_key>
CDP_SECRET_KEY=<production_secret>
CDP_BASE_URL=https://ingestlog.vietcredit.com.vnTích hợp React Navigation đầy đủ
Setup hoàn chỉnh với auto screen tracking và AppState flush:
// src/lib/cdp.ts
import { CdpRnSdk } from 'vc-cdp-rn-sdk';
let _cdp: CdpRnSdk | null = null;
export async function initCdp(): Promise<CdpRnSdk> {
_cdp = await CdpRnSdk.create({
apiKey: process.env.CDP_API_KEY!,
secretKey: process.env.CDP_SECRET_KEY!,
source: 'DOP',
serviceName: 'VC_APP',
baseUrl: process.env.CDP_BASE_URL,
debug: __DEV__,
});
return _cdp;
}
export function getCdp(): CdpRnSdk {
if (!_cdp) throw new Error('CDP SDK chưa được khởi tạo. Gọi initCdp() trước.');
return _cdp;
}// App.tsx
import React, { useEffect, useRef } from 'react';
import { NavigationContainer, useNavigationContainerRef } from '@react-navigation/native';
import { AppState, AppStateStatus } from 'react-native';
import { initCdp, getCdp } from './lib/cdp';
export default function App() {
const navRef = useNavigationContainerRef();
const appState = useRef(AppState.currentState);
useEffect(() => {
initCdp();
}, []);
// Flush khi app vào background
useEffect(() => {
const sub = AppState.addEventListener('change', async (next: AppStateStatus) => {
if (appState.current === 'active' && next.match(/inactive|background/)) {
await getCdp().flush();
}
appState.current = next;
});
return () => sub.remove();
}, []);
return (
<NavigationContainer
ref={navRef}
onReady={() => {
const name = navRef.getCurrentRoute()?.name;
if (name) getCdp().screen(name);
}}
onStateChange={() => {
const name = navRef.getCurrentRoute()?.name;
if (name) getCdp().screen(name);
}}
>
<RootNavigator />
</NavigationContainer>
);
}// Hook tiện lợi
// hooks/useCdp.ts
import { getCdp } from '../lib/cdp';
import type { CdpRnSdk } from 'vc-cdp-rn-sdk';
export function useCdp(): CdpRnSdk {
return getCdp();
}// Identify sau đăng nhập
async function handleLogin(phone: string, password: string) {
const user = await authApi.login(phone, password);
await getCdp().identifyUser(user.id, {
phone: user.phone,
email: user.email,
full_name: user.name,
});
navigation.navigate('Home');
}
// Đăng xuất
async function handleLogout() {
await authApi.logout();
await getCdp().destroy_session();
navigation.reset({ index: 0, routes: [{ name: 'Login' }] });
}API Reference
Factory
static async create(config: CdpRnConfig): Promise<CdpRnSdk>CdpRnConfig
interface CdpRnConfig {
apiKey: string; // bắt buộc
secretKey: string; // bắt buộc
source: string; // bắt buộc — kênh tích hợp
serviceName: string; // bắt buộc — VC_APP | DOP | LOS | LMS
baseUrl?: string; // mặc định staging
isTest?: boolean; // mặc định false
debug?: boolean; // mặc định false
batchSize?: number; // mặc định 10
batchInterval?: number; // mặc định 5000 ms
enableEncryption?: boolean; // mặc định true
notificationApiBaseUrl?: string; // domain API notification-inbox (khác baseUrl)
}Profile Methods
async identifyUser(userId: string, traits?: UserTraits, partyType?: 'PERSON' | 'ORG'): Promise<this>
async identify(userId: string, traits?: UserTraits): Promise<this> // alias
async updateProfile(payload: {
profile_id?: string;
traits?: UserTraits;
consents?: ConsentInfo[];
scores?: ScoreInfo[];
products?: ProductInfo[];
metadata?: Record<string, unknown>;
}): Promise<ProfileResponse | undefined>Trait Setter Methods (chainable → this)
add_first_name(v: string): this
add_last_name(v: string): this
add_user_name(v: string): this
add_email(v: string): this
add_mobile(v: string): this
add_gender(v: 'M' | 'F' | 'O'): this
add_birthday(v: string | Date): this
add_idcard(v: string): this
add_old_idcard(v: string): this
add_address(v: string): this
add_occupation(v: string): this
add_nationality(v: string): this
add_marital_status(v: string): this
add_religion(v: string): this
add_zalo_id(v: string): this
add_tiktok_id(v: string): this
add_user_attribute(name: string, value: unknown): this
add_user_attributes(attrs: UserTraits): this
setUserAttributes(attrs: UserTraits): thisConsent / Score / Product / Campaign Methods
setConsents(consents: ConsentInfo[]): this
setScores(scores: ScoreInfo[]): this
setProducts(products: ProductInfo[]): this
setMetadata(meta: Record<string, unknown>): this
setCampaign(info: CampaignInfo): this
setPlatform(info: PlatformInfo): this
setPushToken(token: string): thisEvent Methods
track(eventName: string, properties?: Record<string, unknown>): void
screen(screenName: string, properties?: Record<string, unknown>): void
async flush(): Promise<void>Notification Inbox Tracking Methods
async getNotificationInboxList(options?: {
userAppId?: string;
userProfileId?: string;
deviceId?: string;
filter?: string; // mặc định 'all'
perPage?: number; // mặc định 20
cursor?: string;
}): Promise<{
code?: number;
message?: string;
data?: NotificationInboxItem[];
next_cursor?: string | null;
has_more?: boolean;
} | undefined>
async getNotificationInboxDetail(
trackingId: string,
options?: { userAppId?: string; deviceId?: string }
): Promise<{ code?: number; message?: string; data?: NotificationInboxItem } | undefined>
async trackNotificationEvent(
trackingId: string,
eventType: 'delivered' | 'open' | 'click' | 'dismiss' | 'read',
options?: {
userAppId?: string;
userProfileId?: string;
deviceId?: string;
platform?: string;
buttonId?: string;
deeplink?: string;
currentScreen?: string;
metadata?: unknown[];
}
): Promise<{ code?: number; message?: string } | undefined>
bindNotificationTracking(notiSdk: NotiSdkLike): () => void // trả về unsubscribeSession & Identity Methods
getUserIdentities(): UserIdentities
getProfileId(): string | null
getDeviceId(): string
getSessionId(): string
getUserAttribute(name: keyof UserTraits): unknown
async destroy_session(): Promise<this>
async reset(): Promise<this> // alias
async destroy(): Promise<void> // cleanup timers + flushTypes
interface UserTraits {
full_name?: string;
first_name?: string;
last_name?: string;
phone?: string; // 🔒 PII
email?: string; // 🔒 PII
gender?: 'M' | 'F' | 'O';
dob?: string; // 'yyyy-MM-dd'
idcard?: string; // 🔒 PII
old_idcard?: string; // 🔒 PII
address?: string; // 🔒 PII
occupation?: string;
nationality?: string;
marital_status?: string;
religion?: string; // 🔒 PII
zalo_id?: string;
tiktok_id?: string;
mps_verified?: boolean;
kyc_status?: string;
[key: string]: unknown;
}
interface ConsentInfo {
consent_type: string;
status: boolean;
consent_date?: string; // 'yyyy-MM-dd'
}
interface ScoreInfo {
score_type: string;
score_value: number;
score_date?: string;
}
interface ProductInfo {
product_code: string;
product_name?: string;
status?: string;
}
interface UserIdentities {
user_id: string | null;
profile_id: string | null;
anonymous_id: string;
device_id: string;
session_id: string;
}
interface ProfileResponse {
code: number;
message: string;
transaction_id: string;
profile_id: string;
}
type NotificationEventType = 'delivered' | 'open' | 'click' | 'dismiss' | 'read';
interface NotificationTrackOptions {
userAppId?: string;
userProfileId?: string;
deviceId?: string;
platform?: string;
buttonId?: string;
deeplink?: string;
currentScreen?: string;
metadata?: unknown[];
}
interface NotificationTrackResponse {
code?: number;
message?: string;
[key: string]: unknown;
}
interface NotificationInboxItem {
tracking_id?: string;
title?: string;
body?: string;
event_type?: NotificationEventType;
deeplink?: string;
read_at?: string | null;
created_at?: string;
[key: string]: unknown;
}
/** Shape tối thiểu của message do react-native-noti-sdk trả về. */
interface NotiMessageLike {
id?: string;
title?: string;
body?: string;
data?: Record<string, string>;
}
/** Duck-typed — khớp với API react-native-noti-sdk, không cần import trực tiếp package đó. */
interface NotiSdkLike {
onMessage: (handler: (message: NotiMessageLike) => void) => () => void;
onNotificationOpened: (handler: (message: NotiMessageLike) => void) => () => void;
getInitialNotification?: () => Promise<NotiMessageLike | null>;
}