시작하기
퍼널 정의, 규칙 설정, coordinator 초기화 세 단계로 첫 넛지를 구현합니다.
설치
bash
pnpm add @liner-engineering/orbit-core구현 단계
1. 퍼널 정의
순차 단계를 통한 사용자 진행 상황을 추적합니다.
typescript
const paywallExitingThreadFunnel = {
funnelId: 'paywall--exiting-thread',
scopes: ['session'],
steps: [(e) => e.name === 'make_chat', (e) => e.name === 'click_icon_to_main_view'],
};2. 규칙 설정
트리거 조건, 타겟팅, 노출 제한을 정의합니다.
typescript
const paywallRule = {
nudgeId: 'paywall',
when: (t) => t.type === 'funnel_completed' && t.funnelId === 'paywall--exiting-thread',
cooldownDuration: '3d',
maxPerWindow: 3,
windowDuration: '3d',
personalTiers: ['free'],
buildAction: () => ({
type: 'paywall',
data: { entryType: 'exiting_thread' },
}),
};3. Coordinator 초기화
typescript
const coordinator = startCoordinator({
funnels: [paywallExitingThreadFunnel],
nudgeRules: [paywallRule],
runner: {
run: async (action) => {
if (action.type === 'paywall') {
showPaywall(action.data);
}
},
},
cooldownStorage: new NudgeCooldownStorage({ storage: localStorage }),
exposureStorage: new NudgeExposureLogStorage({ storage: localStorage }),
});4. 이벤트 추적
typescript
const tracker = generateEventTracker({
getUserId: () => currentUser.id,
getSessionId: () => currentSession.id,
});
tracker.track('make_chat');
tracker.track('click_icon_to_main_view');React 통합
typescript
import { useEffect, useRef } from 'react';
function App() {
const coordinatorRef = useRef(null);
useEffect(() => {
coordinatorRef.current = startCoordinator({
funnels: [paywallExitingThreadFunnel],
nudgeRules: [paywallRule],
runner: { run: async (action) => { /* handle action */ } },
cooldownStorage: new NudgeCooldownStorage({ storage: localStorage }),
exposureStorage: new NudgeExposureLogStorage({ storage: localStorage }),
});
return () => coordinatorRef.current?.cleanup();
}, []);
return <YourApp />;
}React Native 통합 (MMKV)
typescript
import { useEffect, useRef } from 'react';
import { MMKV } from 'react-native-mmkv';
const storage = new MMKV();
class MMKVStorageAdapter {
get(key) {
const value = storage.getString(key);
return value ? JSON.parse(value) : undefined;
}
set(key, value) {
storage.set(key, JSON.stringify(value));
}
delete(key) {
storage.delete(key);
}
}
function App() {
const coordinatorRef = useRef(null);
useEffect(() => {
const mmkvStorage = new MMKVStorageAdapter();
coordinatorRef.current = startCoordinator({
funnels: [paywallExitingThreadFunnel],
nudgeRules: [paywallRule],
runner: { run: async (action) => { /* 액션 처리 */ } },
cooldownStorage: new NudgeCooldownStorage({ storage: mmkvStorage }),
exposureStorage: new NudgeExposureLogStorage({ storage: mmkvStorage }),
});
return () => coordinatorRef.current?.cleanup();
}, []);
return <YourApp />;
}전체 예제
typescript
import {
startCoordinator,
generateEventTracker,
NudgeCooldownStorage,
NudgeExposureLogStorage,
} from '@liner-engineering/orbit-core/nudge-coordinator';
const funnel = {
funnelId: 'paywall--exiting-thread',
scopes: ['session'],
steps: [(e) => e.name === 'make_chat', (e) => e.name === 'click_icon_to_main_view'],
};
const rule = {
nudgeId: 'paywall',
when: (t) => t.type === 'funnel_completed' && t.funnelId === 'paywall--exiting-thread',
cooldownDuration: '3d',
maxPerWindow: 3,
windowDuration: '3d',
personalTiers: ['free'],
buildAction: () => ({ type: 'paywall', data: { entryType: 'exiting_thread' } }),
};
const coordinator = startCoordinator({
funnels: [funnel],
nudgeRules: [rule],
runner: { run: (action) => action.type === 'paywall' && showPaywall(action.data) },
cooldownStorage: new NudgeCooldownStorage({ storage: localStorage }),
exposureStorage: new NudgeExposureLogStorage({ storage: localStorage }),
});
const tracker = generateEventTracker({
getUserId: () => 'user123',
getSessionId: () => 'session456',
});
tracker.track('make_chat');
tracker.track('click_icon_to_main_view');