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

vc-cdp-rn-sdk

v0.2.2

Published

CDP SDK for React Native - Customer Data Platform tracking for iOS and Android

Downloads

1,820

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ả iOSAndroid.


Mục lục


Cài đặt

npm install vc-cdp-rn-sdk

Peer Dependencies

npm install @react-native-async-storage/async-storage
npm install react-native-device-info

iOS

cd ios && pod install

Android

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ạo session_id mớ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 event

Storage 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/clipboard
cd ios && pod install

Cấu hình Firebase (phía app tích hợp):

  • Android: thêm android/app/google-services.json + plugin com.google.gms.google-services.
  • iOS: thêm GoogleService-Info.plist và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()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_NOTIFICATIONS qua Notifee khi gọi requestPermission() / init(). Đảm bảo compileSdkVersion >= 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ở UIActivityViewController khi 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:       false

Cô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 secretKey hay apiKey vào console production.
  • Bật debug: false trê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.vn

Tí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): this

Consent / 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): this

Event 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ề unsubscribe

Session & 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 + flush

Types

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>;
}