@vircle/sdk-web 0.2.0 → 0.2.1
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.
- package/README.md +476 -138
- package/dist/index.d.ts +481 -245
- package/dist/index.esm.js +852 -258
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +854 -261
- package/dist/index.js.map +1 -1
- package/dist/vircle-web-sdk.min.js +1 -1
- package/dist/vircle-web-sdk.min.js.map +1 -1
- package/dist/vircle-web-sdk.standalone.esm.js +1 -1
- package/dist/vircle-web-sdk.standalone.esm.js.map +1 -1
- package/package.json +79 -79
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# @vircle/sdk-web
|
|
2
2
|
|
|
3
|
-
VircleCore를 확장하여 웹 환경에 특화된 기능을 제공하는 웹 애플리케이션용 Vircle Analytics SDK입니다. 자동 추적, 브라우저 컨텍스트 수집, 웹 스토리지
|
|
3
|
+
VircleCore를 확장하여 웹 환경에 특화된 기능을 제공하는 웹 애플리케이션용 Vircle Analytics SDK입니다. 자동 추적, 브라우저 컨텍스트 수집, 웹 스토리지 지원, 하이브리드 암호화 등 웹 환경에 최적화된 기능을 제공합니다.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 주요 기능
|
|
6
6
|
|
|
7
7
|
### 핵심 기능
|
|
8
8
|
- **VircleCore 상속**: 코어 SDK의 모든 기능 + 웹 특화 기능
|
|
@@ -13,13 +13,19 @@ VircleCore를 확장하여 웹 환경에 특화된 기능을 제공하는 웹
|
|
|
13
13
|
### 성능 최적화
|
|
14
14
|
- **requestIdleCallback**: UI 블로킹 없는 백그라운드 처리
|
|
15
15
|
- **컨텍스트 캐싱**: 반복적인 DOM 접근 방지
|
|
16
|
-
-
|
|
16
|
+
- **스마트 스토리지**: IndexedDB/LocalStorage 자동 선택으로 대용량 데이터 지원
|
|
17
17
|
- **이벤트 배칭**: 네트워크 요청 최소화
|
|
18
|
+
- **큐 크기 제한**: TaskScheduler 큐 오버플로우 방지 (기본 1000)
|
|
19
|
+
|
|
20
|
+
### 세션 관리
|
|
21
|
+
- **세션 타임아웃**: 비활성 상태 감지 후 자동 세션 로테이션 (기본 30분)
|
|
22
|
+
- **sendBeacon**: 페이지 종료 시 이벤트 손실 방지
|
|
18
23
|
|
|
19
24
|
### 보안 및 프라이버시
|
|
20
|
-
- **페이로드 암호화**: 하이브리드 암호화 (AES-256-GCM + RSA-OAEP)
|
|
25
|
+
- **페이로드 암호화**: Web Crypto API 기반 하이브리드 암호화 (AES-256-GCM + RSA-OAEP)
|
|
21
26
|
- **추적 제외**: data-vircle-ignore 속성으로 민감 정보 보호
|
|
22
27
|
- **안전한 에러 처리**: SDK 안정성을 위한 모든 외부 API 호출 보호
|
|
28
|
+
- **전용 에러 클래스**: 상세한 에러 코드 및 디버깅 정보 제공
|
|
23
29
|
|
|
24
30
|
## 설치
|
|
25
31
|
|
|
@@ -33,21 +39,25 @@ pnpm add @vircle/sdk-web
|
|
|
33
39
|
|
|
34
40
|
## 예제 실행
|
|
35
41
|
|
|
42
|
+
예제는 `packages/demo-web` 디렉토리에서 확인할 수 있습니다:
|
|
43
|
+
|
|
36
44
|
```bash
|
|
37
45
|
# 저장소 클론 후
|
|
38
|
-
cd packages/
|
|
46
|
+
cd packages/demo-web
|
|
39
47
|
|
|
40
|
-
#
|
|
41
|
-
|
|
42
|
-
pnpm build
|
|
48
|
+
# ESM 예제
|
|
49
|
+
open esm/index.html
|
|
43
50
|
|
|
44
|
-
# 예제
|
|
45
|
-
pnpm
|
|
51
|
+
# React 예제
|
|
52
|
+
cd react && pnpm install && pnpm start
|
|
46
53
|
|
|
47
|
-
#
|
|
54
|
+
# Next.js 예제
|
|
55
|
+
cd nextjs && pnpm install && pnpm dev
|
|
48
56
|
```
|
|
49
57
|
|
|
50
|
-
|
|
58
|
+
자세한 내용은 [demo-web README](../demo-web/README.md)를 참조하세요.
|
|
59
|
+
|
|
60
|
+
## 빠른 시작
|
|
51
61
|
|
|
52
62
|
### 기본 사용법
|
|
53
63
|
|
|
@@ -57,27 +67,32 @@ import { VircleWeb } from '@vircle/sdk-web';
|
|
|
57
67
|
// SDK 초기화 (VircleCore 설정 + 웹 전용 설정)
|
|
58
68
|
const vircle = new VircleWeb({
|
|
59
69
|
apiKey: 'your-api-key',
|
|
60
|
-
environment: 'production',
|
|
61
|
-
|
|
70
|
+
environment: 'production',
|
|
71
|
+
|
|
62
72
|
// 배치 처리 설정 (VircleCore 기능)
|
|
63
73
|
batch: {
|
|
64
|
-
size:
|
|
65
|
-
timeout: 5000,
|
|
66
|
-
flushInterval: 10000,
|
|
74
|
+
size: 50, // 기본값: 50
|
|
75
|
+
timeout: 5000, // 기본값: 5000ms
|
|
76
|
+
flushInterval: 10000, // 기본값: 10000ms
|
|
67
77
|
},
|
|
68
|
-
|
|
78
|
+
|
|
69
79
|
// 웹 전용 자동 추적 설정
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
80
|
+
trackPageViews: true,
|
|
81
|
+
trackClicks: true,
|
|
82
|
+
trackForms: true,
|
|
83
|
+
trackErrors: true,
|
|
84
|
+
singlePageApp: true,
|
|
85
|
+
|
|
86
|
+
// 암호화 활성화
|
|
87
|
+
enableEncryption: true,
|
|
88
|
+
|
|
89
|
+
// 스토리지 설정
|
|
90
|
+
storageType: 'auto',
|
|
91
|
+
storagePrefix: 'vircle_',
|
|
76
92
|
}, {
|
|
77
93
|
// 웹 전용 옵션
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
contextCacheTime: 300000, // 컨텍스트 캐싱 시간
|
|
94
|
+
flushOnUnload: true,
|
|
95
|
+
contextCacheTime: 300000,
|
|
81
96
|
});
|
|
82
97
|
|
|
83
98
|
// SDK 초기화 필수!
|
|
@@ -105,11 +120,9 @@ await vircle.identify('user-123', {
|
|
|
105
120
|
|
|
106
121
|
const vircle = new VircleWeb({
|
|
107
122
|
apiKey: 'your-api-key',
|
|
108
|
-
environment: 'production',
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
clicks: true,
|
|
112
|
-
},
|
|
123
|
+
environment: 'production',
|
|
124
|
+
trackPageViews: true,
|
|
125
|
+
trackClicks: true,
|
|
113
126
|
});
|
|
114
127
|
|
|
115
128
|
await vircle.initialize();
|
|
@@ -124,7 +137,8 @@ await vircle.identify('user-123', {
|
|
|
124
137
|
```html
|
|
125
138
|
<script src="https://cdn.jsdelivr.net/npm/@vircle/sdk-web/dist/vircle-web-sdk.min.js"></script>
|
|
126
139
|
<script>
|
|
127
|
-
|
|
140
|
+
// UMD 빌드는 VircleWebSDK 전역 변수로 VircleWeb 클래스를 직접 노출합니다
|
|
141
|
+
const vircle = new VircleWebSDK({
|
|
128
142
|
apiKey: 'your-api-key',
|
|
129
143
|
});
|
|
130
144
|
|
|
@@ -134,26 +148,29 @@ await vircle.identify('user-123', {
|
|
|
134
148
|
</script>
|
|
135
149
|
```
|
|
136
150
|
|
|
137
|
-
##
|
|
151
|
+
## 아키텍처
|
|
138
152
|
|
|
139
153
|
```
|
|
140
154
|
┌────────────────────────────────────────────┐
|
|
141
155
|
│ VircleWeb │
|
|
142
156
|
│ (VircleCore 상속 + 웹 전용 기능) │
|
|
143
157
|
├────────────────────────────────────────────┤
|
|
144
|
-
│ • 자동 추적
|
|
158
|
+
│ • 자동 추적 (페이지뷰/클릭/폼/에러/SPA) │
|
|
145
159
|
│ • 웹 컨텍스트 수집 (WebContextCollector) │
|
|
146
|
-
│ •
|
|
147
|
-
│ •
|
|
160
|
+
│ • 세션 타임아웃 / 로테이션 │
|
|
161
|
+
│ • sendBeacon (페이지 이탈 보호) │
|
|
162
|
+
│ • StorageFactory (IndexedDB/LocalStorage) │
|
|
163
|
+
│ • 하이브리드 암호화 (WebExtendedCryptoAdapter)│
|
|
164
|
+
│ • 에러 클래스 (VircleWebError 계열) │
|
|
148
165
|
└────────────────────┬───────────────────────┘
|
|
149
166
|
│ extends
|
|
150
167
|
┌────────────────────┴───────────────────────┐
|
|
151
168
|
│ VircleCore │
|
|
152
|
-
│
|
|
169
|
+
│ (이벤트/전송/플러그인/암호화/원격 설정/스토리지) │
|
|
153
170
|
└────────────────────────────────────────────┘
|
|
154
171
|
```
|
|
155
172
|
|
|
156
|
-
##
|
|
173
|
+
## 웹 전용 기능 상세
|
|
157
174
|
|
|
158
175
|
### 자동 추적 시스템
|
|
159
176
|
|
|
@@ -168,73 +185,264 @@ SDK는 `setupAutoTracking` 메서드를 통해 다양한 브라우저 이벤트
|
|
|
168
185
|
#### 클릭 추적
|
|
169
186
|
- **전역 이벤트 위임**: `document`에 단일 리스너로 모든 클릭 감지
|
|
170
187
|
- **요소 정보 수집**: 태그명, 텍스트, 클래스, ID
|
|
188
|
+
- **추적 대상**: `A`, `BUTTON`, `INPUT`, `SELECT`, `TEXTAREA` 요소만 추적
|
|
171
189
|
- **추적 제외**: `data-vircle-ignore` 속성으로 민감 요소 제외
|
|
190
|
+
- **data-* 속성 수집**: idle 시간에 비동기로 추가 data 속성 수집
|
|
172
191
|
|
|
173
192
|
#### 폼 제출 추적
|
|
174
193
|
- **자동 감지**: 모든 `form` 태그의 `submit` 이벤트
|
|
175
|
-
- **수집 정보**: 폼 ID, action, method
|
|
194
|
+
- **수집 정보**: 폼 ID, name, action, method
|
|
176
195
|
- **보안**: 폼 데이터는 수집하지 않음
|
|
177
196
|
|
|
178
197
|
#### 에러 추적
|
|
179
|
-
- **JavaScript 에러**: `
|
|
180
|
-
- **Promise 거부**: `
|
|
181
|
-
- **에러 정보**: 메시지, 스택, 파일명, 라인 번호
|
|
198
|
+
- **JavaScript 에러**: `ErrorEvent` 리스너 (메시지, 파일명, 라인, 컬럼, 스택)
|
|
199
|
+
- **Promise 거부**: `PromiseRejectionEvent` 리스너 (reason)
|
|
182
200
|
|
|
183
201
|
### 웹 컨텍스트 수집 (WebContextCollector)
|
|
184
202
|
|
|
185
|
-
|
|
203
|
+
`collect()` 호출 시 `device`, `page`, `app`, `custom` 4가지 컨텍스트를 **병렬로** 수집합니다.
|
|
204
|
+
|
|
205
|
+
#### device (DeviceContext)
|
|
206
|
+
|
|
207
|
+
User-Agent 기반 디바이스/브라우저/OS 감지 및 화면 정보 수집
|
|
208
|
+
|
|
209
|
+
| 필드 | 타입 | 소스 | 설명 |
|
|
210
|
+
|------|------|------|------|
|
|
211
|
+
| `type` | `'mobile' \| 'tablet' \| 'desktop'` | `navigator.userAgent` | UA 패턴 매칭으로 판별 |
|
|
212
|
+
| `os` | `string` | `navigator.userAgent` | Windows, macOS, Linux, Android, iOS, Chrome OS |
|
|
213
|
+
| `osVersion` | `string \| undefined` | `navigator.userAgent` | OS 버전 (예: `14.0`) |
|
|
214
|
+
| `browser` | `string` | `navigator.userAgent` | Edge, Chrome, Firefox, Safari, Opera, IE |
|
|
215
|
+
| `browserVersion` | `string \| undefined` | `navigator.userAgent` | 브라우저 버전 (예: `120.0`) |
|
|
216
|
+
| `screenResolution` | `string` | `screen.width/height` | `"1920x1080"` 형태 |
|
|
217
|
+
| `viewport` | `{ width, height }` | `window.innerWidth/Height` | 현재 뷰포트 크기 |
|
|
218
|
+
| `language` | `string` | `navigator.language` | 브라우저 언어 (예: `ko-KR`) |
|
|
219
|
+
| `timezone` | `string` | `Intl.DateTimeFormat` | IANA 타임존 (예: `Asia/Seoul`) |
|
|
220
|
+
|
|
221
|
+
#### page (PageContext)
|
|
222
|
+
|
|
223
|
+
현재 페이지 URL 파싱 결과
|
|
224
|
+
|
|
225
|
+
| 필드 | 타입 | 소스 | 설명 |
|
|
226
|
+
|------|------|------|------|
|
|
227
|
+
| `url` | `string` | `window.location.href` | 전체 URL |
|
|
228
|
+
| `title` | `string` | `document.title` | 페이지 제목 |
|
|
229
|
+
| `path` | `string` | `URL.pathname` | 경로 (예: `/products`) |
|
|
230
|
+
| `search` | `string` | `URL.search` | 쿼리스트링 (예: `?category=electronics`) |
|
|
231
|
+
| `hash` | `string` | `URL.hash` | 해시 프래그먼트 |
|
|
232
|
+
|
|
233
|
+
#### app (AppContext)
|
|
234
|
+
|
|
235
|
+
애플리케이션 메타 정보 (HTML meta 태그 기반)
|
|
236
|
+
|
|
237
|
+
| 필드 | 타입 | 소스 | 설명 |
|
|
238
|
+
|------|------|------|------|
|
|
239
|
+
| `name` | `string` | `<meta name="application-name">` 또는 `<meta property="og:site_name">` 또는 `document.title` | 앱 이름 |
|
|
240
|
+
| `version` | `string \| undefined` | `<meta name="version">` | 앱 버전 |
|
|
241
|
+
| `environment` | `'development' \| 'production'` | `window.location.hostname` | localhost/staging → development, 그 외 → production |
|
|
242
|
+
|
|
243
|
+
#### custom (Record<string, unknown>)
|
|
244
|
+
|
|
245
|
+
추가 브라우저 환경 정보 (가용한 경우에만 수집)
|
|
246
|
+
|
|
247
|
+
| 필드 | 타입 | 조건 | 설명 |
|
|
248
|
+
|------|------|------|------|
|
|
249
|
+
| `referrer` | `string` | `document.referrer` 존재 시 | 이전 페이지 URL |
|
|
250
|
+
| `campaign` | `{ source, medium, campaign, term, content }` | URL에 `utm_*` 파라미터 존재 시 | UTM 캠페인 파라미터 (`utm_` 접두사 제거) |
|
|
251
|
+
| `cookieEnabled` | `boolean` | 항상 | 쿠키 활성화 여부 |
|
|
252
|
+
| `doNotTrack` | `boolean` | `navigator.doNotTrack` 존재 시 | DNT 설정 여부 |
|
|
253
|
+
| `connection` | `{ effectiveType, downlink, rtt, saveData }` | Network Information API 지원 시 | 네트워크 연결 정보 |
|
|
254
|
+
| `memory` | `{ usedJSHeapSize, totalJSHeapSize, jsHeapSizeLimit }` | `performance.memory` 지원 시 (Chrome 전용) | JS 힙 메모리 사용량 |
|
|
255
|
+
|
|
256
|
+
#### 수집 결과 예시
|
|
186
257
|
|
|
187
258
|
```typescript
|
|
188
259
|
{
|
|
189
260
|
device: {
|
|
190
|
-
type: 'desktop',
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
language: 'ko-KR', // 언어
|
|
200
|
-
timezone: 'Asia/Seoul' // 타임존
|
|
261
|
+
type: 'desktop',
|
|
262
|
+
os: 'macOS',
|
|
263
|
+
osVersion: '14.0',
|
|
264
|
+
browser: 'Chrome',
|
|
265
|
+
browserVersion: '120.0',
|
|
266
|
+
screenResolution: '1920x1080',
|
|
267
|
+
viewport: { width: 1200, height: 800 },
|
|
268
|
+
language: 'ko-KR',
|
|
269
|
+
timezone: 'Asia/Seoul'
|
|
201
270
|
},
|
|
202
271
|
page: {
|
|
203
272
|
url: 'https://example.com/products?category=electronics',
|
|
204
273
|
title: '제품 목록',
|
|
205
274
|
path: '/products',
|
|
206
275
|
search: '?category=electronics',
|
|
207
|
-
hash: ''
|
|
208
|
-
|
|
276
|
+
hash: ''
|
|
277
|
+
},
|
|
278
|
+
app: {
|
|
279
|
+
name: 'My Store',
|
|
280
|
+
version: '2.1.0',
|
|
281
|
+
environment: 'production'
|
|
209
282
|
},
|
|
210
283
|
custom: {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
284
|
+
referrer: 'https://google.com',
|
|
285
|
+
campaign: {
|
|
286
|
+
source: 'google',
|
|
287
|
+
medium: 'cpc',
|
|
288
|
+
campaign: 'summer_sale'
|
|
289
|
+
},
|
|
290
|
+
cookieEnabled: true,
|
|
291
|
+
doNotTrack: false,
|
|
292
|
+
connection: {
|
|
216
293
|
effectiveType: '4g',
|
|
217
|
-
downlink: 10.0
|
|
294
|
+
downlink: 10.0,
|
|
295
|
+
rtt: 50,
|
|
296
|
+
saveData: false
|
|
297
|
+
},
|
|
298
|
+
memory: {
|
|
299
|
+
usedJSHeapSize: 12345678,
|
|
300
|
+
totalJSHeapSize: 33554432,
|
|
301
|
+
jsHeapSizeLimit: 2147483648
|
|
218
302
|
}
|
|
219
303
|
}
|
|
220
304
|
}
|
|
221
305
|
```
|
|
222
306
|
|
|
223
|
-
###
|
|
307
|
+
### 스토리지 어댑터
|
|
224
308
|
|
|
225
|
-
|
|
309
|
+
SDK는 `StorageFactory`를 통해 환경에 최적화된 스토리지를 자동 선택합니다:
|
|
226
310
|
|
|
311
|
+
#### IndexedDB 어댑터 (권장)
|
|
312
|
+
- **대용량 지원**: LocalStorage 5MB 제한 없이 대량 이벤트 저장
|
|
313
|
+
- **비동기 처리**: UI 블로킹 없는 백그라운드 저장
|
|
314
|
+
- **트랜잭션 지원**: 데이터 무결성 보장
|
|
315
|
+
- **LRU 정리**: 타임스탬프 인덱스 기반 오래된 데이터 자동 정리
|
|
316
|
+
|
|
317
|
+
#### LocalStorage 어댑터
|
|
227
318
|
- **자동 용량 관리**: QuotaExceededError 시 LRU 캐시 정리
|
|
228
319
|
- **접두사 격리**: `vircle_` 접두사로 다른 데이터와 분리
|
|
229
320
|
- **타임스탬프 추적**: 데이터 저장 시간 기록
|
|
230
321
|
- **안전한 폴백**: LocalStorage 불가 시 경고 후 계속 작동
|
|
231
322
|
|
|
323
|
+
#### 스토리지 타입 선택
|
|
324
|
+
|
|
325
|
+
```typescript
|
|
326
|
+
const vircle = new VircleWeb({
|
|
327
|
+
apiKey: 'your-api-key',
|
|
328
|
+
storageType: 'auto', // 'auto' | 'localStorage' | 'indexedDB'
|
|
329
|
+
});
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
| 타입 | 설명 |
|
|
333
|
+
|------|------|
|
|
334
|
+
| `auto` (기본값) | IndexedDB 우선, 미지원 시 LocalStorage 폴백 |
|
|
335
|
+
| `localStorage` | 항상 LocalStorage 사용 |
|
|
336
|
+
| `indexedDB` | 항상 IndexedDB 사용 |
|
|
337
|
+
|
|
338
|
+
`StorageFactory`는 동기(`create`) 및 비동기(`createAsync`) 생성을 모두 지원합니다. 비동기 방식은 Safari Private 모드 등에서 IndexedDB 실제 사용 가능 여부를 테스트한 후 안전하게 폴백합니다.
|
|
339
|
+
|
|
340
|
+
### 암호화 (WebExtendedCryptoAdapter)
|
|
341
|
+
|
|
342
|
+
Web Crypto API 기반의 하이브리드 암호화를 제공합니다:
|
|
343
|
+
|
|
344
|
+
```
|
|
345
|
+
데이터 (JSON) → AES-256-GCM 암호화 → 암호문 + Auth Tag
|
|
346
|
+
AES 키 → RSA-OAEP (서버 공개키) → 암호화된 키
|
|
347
|
+
결과: { data, key, iv, authTag, metadata }
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
#### 암호화 활성화
|
|
351
|
+
|
|
352
|
+
```typescript
|
|
353
|
+
const vircle = new VircleWeb({
|
|
354
|
+
apiKey: 'your-api-key',
|
|
355
|
+
enableEncryption: true, // Web Crypto API 기반 암호화 활성화
|
|
356
|
+
});
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
#### 제공 기능
|
|
360
|
+
- **하이브리드 암호화**: AES-256-GCM (데이터) + RSA-OAEP (키 래핑)
|
|
361
|
+
- **UUID v4 생성**: `crypto.randomUUID` 또는 `getRandomValues` 폴백
|
|
362
|
+
- **HMAC-SHA256 서명**: 페이로드 무결성 검증
|
|
363
|
+
- **자동 가용성 감지**: Web Crypto API 미지원 시 암호화 자동 비활성화
|
|
364
|
+
|
|
365
|
+
#### 암호화된 페이로드 구조
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
interface EncryptedPayload {
|
|
369
|
+
data: string; // Base64 인코딩된 AES-GCM 암호문
|
|
370
|
+
key: string; // Base64 인코딩된 RSA-OAEP 암호화 AES 키
|
|
371
|
+
iv: string; // Base64 인코딩된 초기화 벡터 (96-bit)
|
|
372
|
+
authTag: string; // Base64 인코딩된 GCM 인증 태그 (128-bit)
|
|
373
|
+
metadata: {
|
|
374
|
+
algorithm: 'AES-256-GCM';
|
|
375
|
+
keyAlgorithm: 'RSA-OAEP';
|
|
376
|
+
timestamp: string;
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### 세션 타임아웃
|
|
382
|
+
|
|
383
|
+
SDK는 사용자 비활성 상태를 감지하여 자동으로 새 세션을 시작합니다:
|
|
384
|
+
|
|
385
|
+
- **기본 타임아웃**: 30분 (설정 가능)
|
|
386
|
+
- **동작 방식**: `visibilitychange → visible` 이벤트 시 마지막 활동 시간과 비교
|
|
387
|
+
- **세션 로테이션**: 타임아웃 경과 시 새 세션 ID 생성 + `session_start` 이벤트 자동 추적
|
|
388
|
+
- **활동 갱신**: `track()` 호출 시마다 마지막 활동 시간 갱신
|
|
389
|
+
|
|
390
|
+
```typescript
|
|
391
|
+
const vircle = new VircleWeb({
|
|
392
|
+
apiKey: 'your-api-key',
|
|
393
|
+
sessionTimeout: 15 * 60 * 1000, // 15분
|
|
394
|
+
});
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### sendBeacon (페이지 이탈 보호)
|
|
398
|
+
|
|
399
|
+
페이지 종료 시 `navigator.sendBeacon`을 사용하여 대기 중인 이벤트를 안전하게 전송합니다:
|
|
400
|
+
|
|
401
|
+
- **beforeunload**: 페이지 닫기/새로고침 시 큐의 이벤트를 beacon으로 전송
|
|
402
|
+
- **visibilitychange → hidden**: 탭 전환/최소화 시 beacon으로 전송
|
|
403
|
+
- **제약사항**: sendBeacon은 커스텀 헤더를 설정할 수 없으므로 API 키를 요청 body에 포함하며, 암호화는 스킵됩니다
|
|
404
|
+
- **폴백**: `navigator.sendBeacon` 미지원 환경에서는 자동으로 무시됩니다
|
|
405
|
+
|
|
406
|
+
```typescript
|
|
407
|
+
const vircle = new VircleWeb(
|
|
408
|
+
{ apiKey: 'your-api-key' },
|
|
409
|
+
{ flushOnUnload: true }, // 기본값: true
|
|
410
|
+
);
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
### 에러 클래스
|
|
414
|
+
|
|
415
|
+
SDK는 디버깅을 위한 구조화된 에러 클래스를 제공합니다:
|
|
416
|
+
|
|
417
|
+
| 에러 클래스 | 코드 | 설명 |
|
|
418
|
+
|------------|------|------|
|
|
419
|
+
| `VircleWebError` | - | 기본 에러 클래스 (코드, 상세정보 포함) |
|
|
420
|
+
| `VircleConfigError` | `CONFIG_ERROR` | 잘못된 설정 (API 키 누락 등) |
|
|
421
|
+
| `VircleInitializationError` | `INITIALIZATION_ERROR` | 초기화 실패 |
|
|
422
|
+
| `VircleStorageError` | `STORAGE_ERROR` | 스토리지 작업 실패 |
|
|
423
|
+
| `VircleBrowserCompatibilityError` | `BROWSER_COMPATIBILITY_ERROR` | 브라우저 미지원 기능 |
|
|
424
|
+
|
|
425
|
+
```typescript
|
|
426
|
+
import { VircleWebError, VircleConfigError } from '@vircle/sdk-web';
|
|
427
|
+
|
|
428
|
+
try {
|
|
429
|
+
const vircle = new VircleWeb({ apiKey: '' });
|
|
430
|
+
await vircle.initialize();
|
|
431
|
+
} catch (error) {
|
|
432
|
+
if (error instanceof VircleConfigError) {
|
|
433
|
+
console.error('설정 오류:', error.code, error.details);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
```
|
|
437
|
+
|
|
232
438
|
### 성능 최적화 기술
|
|
233
439
|
|
|
234
|
-
- **requestIdleCallback 활용**: UI 블로킹 없는 백그라운드 이벤트 처리
|
|
235
|
-
- **컨텍스트 캐싱**: 브라우저 정보
|
|
440
|
+
- **requestIdleCallback 활용**: TaskScheduler를 통한 UI 블로킹 없는 백그라운드 이벤트 처리
|
|
441
|
+
- **컨텍스트 캐싱**: ContextCache를 통한 브라우저 정보 캐싱 (기본 5분), 중복 수집 방지를 위한 Promise 재사용
|
|
236
442
|
- **이벤트 배칭**: TransportService의 큐를 통한 네트워크 요청 최소화
|
|
237
|
-
- **메모리 관리**: cleanup 시 모든 이벤트 리스너
|
|
443
|
+
- **메모리 관리**: cleanup 시 모든 이벤트 리스너 제거 및 History API 원복으로 누수 방지
|
|
444
|
+
- **페이지 이탈 대응**: `navigator.sendBeacon`을 사용한 페이지 종료 시 안정적 이벤트 전송
|
|
445
|
+
- **큐 크기 제한**: TaskScheduler의 `maxQueueSize`(기본 1000)로 메모리 오버플로우 방지
|
|
238
446
|
|
|
239
447
|
### 추적 제외
|
|
240
448
|
|
|
@@ -255,26 +463,43 @@ SDK는 `setupAutoTracking` 메서드를 통해 다양한 브라우저 이벤트
|
|
|
255
463
|
|
|
256
464
|
### VircleWebConfig
|
|
257
465
|
|
|
466
|
+
`VircleConfig` (코어)를 상속하며 웹 전용 옵션을 추가합니다.
|
|
467
|
+
|
|
258
468
|
```typescript
|
|
259
|
-
interface VircleWebConfig {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
469
|
+
interface VircleWebConfig extends VircleConfig {
|
|
470
|
+
trackPageViews?: boolean; // 자동 페이지뷰 추적 (기본: false)
|
|
471
|
+
trackClicks?: boolean; // 자동 클릭 추적 (기본: false)
|
|
472
|
+
trackForms?: boolean; // 자동 폼 제출 추적 (기본: false)
|
|
473
|
+
trackErrors?: boolean; // 자동 에러 추적 (기본: false)
|
|
474
|
+
singlePageApp?: boolean; // SPA 모드 (기본: false)
|
|
475
|
+
storagePrefix?: string; // 스토리지 키 접두사 (기본: 'vircle_')
|
|
476
|
+
storageType?: StorageType; // 스토리지 타입: 'auto' | 'localStorage' | 'indexedDB' (기본: 'auto')
|
|
477
|
+
enableEncryption?: boolean; // Web Crypto API 암호화 활성화 (기본: false)
|
|
478
|
+
sessionTimeout?: number; // 세션 타임아웃(ms) (기본: 1800000 = 30분)
|
|
268
479
|
}
|
|
269
480
|
```
|
|
270
481
|
|
|
482
|
+
#### VircleConfig (코어) 주요 옵션
|
|
483
|
+
|
|
484
|
+
| 옵션 | 타입 | 기본값 | 설명 |
|
|
485
|
+
|------|------|--------|------|
|
|
486
|
+
| `apiKey` | `string` | (필수) | API 인증 키 |
|
|
487
|
+
| `environment` | `'development' \| 'production'` | `'production'` | 환경 설정 |
|
|
488
|
+
| `debug` | `boolean` | `false` | 디버그 모드 |
|
|
489
|
+
| `batch.size` | `number` | `50` | 배치당 최대 이벤트 수 |
|
|
490
|
+
| `batch.timeout` | `number` | `5000` | 배치 대기 시간(ms) |
|
|
491
|
+
| `batch.flushInterval` | `number` | `10000` | 자동 플러시 간격(ms) |
|
|
492
|
+
| `retry.maxAttempts` | `number` | `3` | 최대 재시도 횟수 |
|
|
493
|
+
| `retry.initialDelay` | `number` | `500` | 초기 백오프 지연(ms) |
|
|
494
|
+
| `privacy.respectDoNotTrack` | `boolean` | `true` | DNT 헤더 존중 |
|
|
495
|
+
| `headers` | `Record<string, string>` | - | 커스텀 HTTP 헤더 |
|
|
496
|
+
|
|
271
497
|
### VircleWebOptions
|
|
272
498
|
|
|
273
499
|
```typescript
|
|
274
|
-
interface VircleWebOptions {
|
|
275
|
-
flushOnUnload?: boolean;
|
|
276
|
-
contextCacheTime?: number; // 컨텍스트 캐시 시간(ms) (기본: 300000)
|
|
277
|
-
enablePerformance?: boolean; // 성능 측정 활성화 (기본: true)
|
|
500
|
+
interface VircleWebOptions extends Partial<VircleCoreOptions> {
|
|
501
|
+
flushOnUnload?: boolean; // 페이지 언로드 시 sendBeacon 전송 (기본: true)
|
|
502
|
+
contextCacheTime?: number; // 컨텍스트 캐시 시간(ms) (기본: 300000 = 5분)
|
|
278
503
|
}
|
|
279
504
|
```
|
|
280
505
|
|
|
@@ -291,14 +516,14 @@ SDK를 초기화하고 자동 추적을 시작합니다.
|
|
|
291
516
|
### 이벤트 추적
|
|
292
517
|
|
|
293
518
|
```typescript
|
|
294
|
-
await vircle.track(
|
|
295
|
-
|
|
296
|
-
properties?:
|
|
519
|
+
await vircle.track<TProperties>(
|
|
520
|
+
name: string,
|
|
521
|
+
properties?: TProperties,
|
|
297
522
|
context?: EventContext
|
|
298
523
|
): Promise<void>
|
|
299
524
|
```
|
|
300
525
|
|
|
301
|
-
커스텀 이벤트를 추적합니다.
|
|
526
|
+
커스텀 이벤트를 추적합니다. `requestIdleCallback`을 활용하여 UI를 블로킹하지 않으며, 호출 시마다 세션의 마지막 활동 시간이 갱신됩니다.
|
|
302
527
|
|
|
303
528
|
### 페이지뷰 추적
|
|
304
529
|
|
|
@@ -309,18 +534,18 @@ await vircle.trackPageView(
|
|
|
309
534
|
): Promise<void>
|
|
310
535
|
```
|
|
311
536
|
|
|
312
|
-
페이지뷰를 수동으로 추적합니다.
|
|
537
|
+
페이지뷰를 수동으로 추적합니다. URL, 제목, 리퍼러 등의 페이지 컨텍스트를 자동 수집합니다.
|
|
313
538
|
|
|
314
539
|
### 사용자 식별
|
|
315
540
|
|
|
316
541
|
```typescript
|
|
317
|
-
await vircle.identify(
|
|
542
|
+
await vircle.identify<TTraits>(
|
|
318
543
|
userId: string,
|
|
319
|
-
traits?:
|
|
544
|
+
traits?: TTraits
|
|
320
545
|
): Promise<void>
|
|
321
546
|
```
|
|
322
547
|
|
|
323
|
-
사용자를 식별하고 속성을 설정합니다.
|
|
548
|
+
사용자를 식별하고 속성을 설정합니다. VircleWeb에서는 호출 시 컨텍스트 캐시를 자동으로 무효화합니다.
|
|
324
549
|
|
|
325
550
|
### 사용자 리셋
|
|
326
551
|
|
|
@@ -328,7 +553,38 @@ await vircle.identify(
|
|
|
328
553
|
await vircle.reset(): Promise<void>
|
|
329
554
|
```
|
|
330
555
|
|
|
331
|
-
사용자
|
|
556
|
+
사용자 정보와 세션을 초기화합니다.
|
|
557
|
+
|
|
558
|
+
### 유틸리티
|
|
559
|
+
|
|
560
|
+
```typescript
|
|
561
|
+
// 현재 페이지 URL
|
|
562
|
+
vircle.getCurrentUrl(): string
|
|
563
|
+
|
|
564
|
+
// 네트워크 연결 상태
|
|
565
|
+
vircle.isOnline(): boolean
|
|
566
|
+
|
|
567
|
+
// SDK 상태 확인
|
|
568
|
+
vircle.getStatus(): {
|
|
569
|
+
isInitialized: boolean;
|
|
570
|
+
sessionId?: string;
|
|
571
|
+
userId?: string;
|
|
572
|
+
pluginCount: number;
|
|
573
|
+
queueSize: number;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// 런타임 메트릭 조회
|
|
577
|
+
vircle.getMetrics(): {
|
|
578
|
+
queueSize: number;
|
|
579
|
+
isProcessing: boolean;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// 이벤트 수동 플러시
|
|
583
|
+
await vircle.flush(): Promise<void>
|
|
584
|
+
|
|
585
|
+
// 이벤트 수동 플러시 (타임아웃 지정)
|
|
586
|
+
await vircle.flush(timeoutMs?: number): Promise<void>
|
|
587
|
+
```
|
|
332
588
|
|
|
333
589
|
### 정리
|
|
334
590
|
|
|
@@ -336,18 +592,64 @@ await vircle.reset(): Promise<void>
|
|
|
336
592
|
await vircle.cleanup(): Promise<void>
|
|
337
593
|
```
|
|
338
594
|
|
|
339
|
-
|
|
595
|
+
SDK가 사용하는 모든 리소스를 정리합니다:
|
|
596
|
+
1. 이벤트 리스너 제거 (`beforeunload`, `error`, `click`, `submit`, `popstate`, `visibilitychange`)
|
|
597
|
+
2. History API 원복 (`pushState`, `replaceState`)
|
|
598
|
+
3. 대기 중인 이벤트 전송 (`flush()`)
|
|
599
|
+
4. 코어 서비스 정리 (`super.cleanup()`)
|
|
600
|
+
|
|
601
|
+
## Exports
|
|
602
|
+
|
|
603
|
+
```typescript
|
|
604
|
+
// 메인 SDK 클래스
|
|
605
|
+
import { VircleWeb } from '@vircle/sdk-web';
|
|
606
|
+
import VircleWeb from '@vircle/sdk-web'; // default export
|
|
607
|
+
|
|
608
|
+
// 설정 타입
|
|
609
|
+
import type { VircleWebConfig, VircleWebOptions } from '@vircle/sdk-web';
|
|
610
|
+
|
|
611
|
+
// 컨텍스트 수집기
|
|
612
|
+
import { WebContextCollector } from '@vircle/sdk-web';
|
|
613
|
+
|
|
614
|
+
// 스토리지
|
|
615
|
+
import { LocalStorageAdapter, IndexedDBAdapter, StorageFactory } from '@vircle/sdk-web';
|
|
616
|
+
import type { StorageType } from '@vircle/sdk-web'; // 'auto' | 'localStorage' | 'indexedDB'
|
|
617
|
+
|
|
618
|
+
// 암호화 어댑터
|
|
619
|
+
import { WebExtendedCryptoAdapter } from '@vircle/sdk-web';
|
|
620
|
+
|
|
621
|
+
// 에러 클래스
|
|
622
|
+
import {
|
|
623
|
+
VircleWebError,
|
|
624
|
+
VircleConfigError,
|
|
625
|
+
VircleInitializationError,
|
|
626
|
+
VircleStorageError,
|
|
627
|
+
VircleBrowserCompatibilityError,
|
|
628
|
+
} from '@vircle/sdk-web';
|
|
629
|
+
|
|
630
|
+
// Core 타입 재수출
|
|
631
|
+
import type {
|
|
632
|
+
VircleConfig,
|
|
633
|
+
EventContext,
|
|
634
|
+
DeviceContext,
|
|
635
|
+
PageContext,
|
|
636
|
+
AppContext,
|
|
637
|
+
UserTraits,
|
|
638
|
+
StorageAdapter,
|
|
639
|
+
} from '@vircle/sdk-web';
|
|
640
|
+
```
|
|
340
641
|
|
|
341
642
|
## 빌드 파일 설명
|
|
342
643
|
|
|
343
|
-
| 파일 | 용도 |
|
|
344
|
-
|
|
345
|
-
| `dist/index.js` |
|
|
346
|
-
| `dist/index.esm.js` |
|
|
347
|
-
| `dist/vircle-web-sdk.standalone.esm.js` |
|
|
348
|
-
| `dist/vircle-web-sdk.min.js` | UMD
|
|
644
|
+
| 파일 | 포맷 | 용도 | `@vircle/sdk-core-ts` | 사용 방법 |
|
|
645
|
+
|------|------|------|----------------------|-----------|
|
|
646
|
+
| `dist/index.js` | CJS | 번들러 (Webpack 등) | 외부 의존성 | `require('@vircle/sdk-web')` |
|
|
647
|
+
| `dist/index.esm.js` | ESM | 번들러 (Vite, Rollup 등) | 외부 의존성 | `import { VircleWeb } from '@vircle/sdk-web'` |
|
|
648
|
+
| `dist/vircle-web-sdk.standalone.esm.js` | ESM | 브라우저 직접 사용 | 번들에 포함 | `<script type="module">` |
|
|
649
|
+
| `dist/vircle-web-sdk.min.js` | UMD | 레거시 브라우저 | 번들에 포함 | `<script src="...">` |
|
|
650
|
+
| `dist/index.d.ts` | DTS | TypeScript 타입 정의 | - | 자동 적용 |
|
|
349
651
|
|
|
350
|
-
|
|
652
|
+
**중요**: 브라우저에서 직접 사용할 때는 반드시 `standalone` 또는 `min` 빌드를 사용하세요. npm 패키지로 사용 시 번들러가 `@vircle/sdk-core-ts`를 함께 번들링합니다.
|
|
351
653
|
|
|
352
654
|
## 브라우저 지원
|
|
353
655
|
|
|
@@ -357,51 +659,49 @@ await vircle.cleanup(): Promise<void>
|
|
|
357
659
|
- iOS Safari: iOS 12+
|
|
358
660
|
- Chrome for Android: Android 6+
|
|
359
661
|
|
|
662
|
+
> 암호화 기능(`enableEncryption`)은 Web Crypto API를 지원하는 브라우저에서만 동작합니다. 미지원 시 자동으로 비활성화됩니다.
|
|
663
|
+
|
|
360
664
|
## 보안 고려사항
|
|
361
665
|
|
|
362
666
|
1. **클라이언트 측 API 키**: 브라우저에서 사용되는 API 키는 공개됩니다. 데이터 수집(Write) 권한만 가진 키를 사용하세요.
|
|
363
667
|
|
|
364
668
|
2. **민감한 데이터**: 민감한 데이터가 수집되지 않도록 `data-vircle-ignore` 속성을 적절히 사용하세요.
|
|
365
669
|
|
|
366
|
-
3.
|
|
670
|
+
3. **스토리지**: SDK는 LocalStorage/IndexedDB를 사용하여 데이터를 임시 저장합니다. 민감한 정보는 저장하지 않습니다.
|
|
671
|
+
|
|
672
|
+
4. **암호화**: `enableEncryption: true` 설정 시 서버 공개키로 이벤트 페이로드를 암호화합니다. 공개키는 CDN(`RemoteConfigService`)에서 자동 fetch됩니다.
|
|
673
|
+
|
|
674
|
+
5. **sendBeacon 전송**: 페이지 종료 시 `sendBeacon`으로 전송되는 이벤트는 커스텀 헤더 설정이 불가하여 암호화/HMAC 서명 없이 전송됩니다. API 키는 요청 body에 포함됩니다. 정상 동작 중에는 `fetch()` 기반의 암호화된 전송이 사용됩니다.
|
|
367
675
|
|
|
368
676
|
## 성능 영향
|
|
369
677
|
|
|
370
|
-
- **번들
|
|
371
|
-
-
|
|
372
|
-
-
|
|
678
|
+
- **번들 크기 (standalone)**: ~106KB (minified, core 포함), npm 패키지는 ~70KB (core 외부 의존성)
|
|
679
|
+
- **이벤트 추적**: requestIdleCallback 기반으로 UI 블로킹 없음
|
|
680
|
+
- **컨텍스트 수집**: 캐싱으로 반복적인 DOM 접근 방지 (기본 5분)
|
|
373
681
|
|
|
374
682
|
## 고급 사용 예제
|
|
375
683
|
|
|
376
684
|
### SPA (Single Page Application) 설정
|
|
377
685
|
|
|
378
686
|
```typescript
|
|
687
|
+
// singlePageApp: true 설정 시 History API (pushState, replaceState)를 래핑하여
|
|
688
|
+
// 라우트 변경을 자동으로 감지하고 페이지뷰를 추적합니다.
|
|
689
|
+
// React Router, Vue Router, Next.js 등 대부분의 SPA 프레임워크와 호환됩니다.
|
|
379
690
|
const vircle = new VircleWeb({
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
})
|
|
384
|
-
|
|
385
|
-
await vircle.initialize()
|
|
386
|
-
|
|
387
|
-
// React Router 예제
|
|
388
|
-
import { useEffect } from 'react'
|
|
389
|
-
import { useLocation } from 'react-router-dom'
|
|
390
|
-
|
|
391
|
-
function App() {
|
|
392
|
-
const location = useLocation()
|
|
393
|
-
|
|
394
|
-
useEffect(() => {
|
|
395
|
-
// 라우트 변경 시 자동으로 페이지뷰가 추적됩니다
|
|
396
|
-
}, [location])
|
|
691
|
+
apiKey: 'your-api-key',
|
|
692
|
+
singlePageApp: true, // History API 래핑
|
|
693
|
+
trackPageViews: true, // 자동 페이지뷰 추적
|
|
694
|
+
});
|
|
397
695
|
|
|
398
|
-
|
|
399
|
-
|
|
696
|
+
await vircle.initialize();
|
|
697
|
+
// 이후 별도의 설정 없이 라우트 변경 시 자동으로 page_view 이벤트가 추적됩니다.
|
|
400
698
|
```
|
|
401
699
|
|
|
402
700
|
### 에러 처리와 함께 사용
|
|
403
701
|
|
|
404
702
|
```typescript
|
|
703
|
+
import { VircleWeb, VircleInitializationError } from '@vircle/sdk-web';
|
|
704
|
+
|
|
405
705
|
try {
|
|
406
706
|
const vircle = new VircleWeb({
|
|
407
707
|
apiKey: 'your-api-key',
|
|
@@ -422,10 +722,27 @@ try {
|
|
|
422
722
|
throw error;
|
|
423
723
|
}
|
|
424
724
|
} catch (error) {
|
|
425
|
-
|
|
725
|
+
if (error instanceof VircleInitializationError) {
|
|
726
|
+
console.error('SDK 초기화 실패:', error.code, error.details);
|
|
727
|
+
}
|
|
426
728
|
}
|
|
427
729
|
```
|
|
428
730
|
|
|
731
|
+
### 암호화 활성화
|
|
732
|
+
|
|
733
|
+
```typescript
|
|
734
|
+
const vircle = new VircleWeb({
|
|
735
|
+
apiKey: 'your-api-key',
|
|
736
|
+
enableEncryption: true, // Web Crypto API 가용 시 자동 활성화
|
|
737
|
+
trackPageViews: true,
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
await vircle.initialize();
|
|
741
|
+
|
|
742
|
+
// 이후 모든 이벤트가 하이브리드 암호화되어 전송됩니다
|
|
743
|
+
await vircle.track('purchase', { amount: 99.99 });
|
|
744
|
+
```
|
|
745
|
+
|
|
429
746
|
### 성능 최적화 설정
|
|
430
747
|
|
|
431
748
|
```typescript
|
|
@@ -434,18 +751,16 @@ const vircle = new VircleWeb(
|
|
|
434
751
|
apiKey: 'your-api-key',
|
|
435
752
|
trackPageViews: true,
|
|
436
753
|
trackClicks: true,
|
|
754
|
+
sessionTimeout: 15 * 60 * 1000, // 15분 세션 타임아웃
|
|
437
755
|
},
|
|
438
756
|
{
|
|
439
|
-
contextCacheTime: 600000, // 10분 캐싱
|
|
440
|
-
flushOnUnload:
|
|
441
|
-
enablePerformance: false, // 성능 추적 비활성화
|
|
757
|
+
contextCacheTime: 600000, // 10분 캐싱 (기본 5분)
|
|
758
|
+
flushOnUnload: true, // sendBeacon으로 페이지 이탈 시 자동 전송 (기본값)
|
|
442
759
|
},
|
|
443
760
|
);
|
|
444
761
|
|
|
445
|
-
// 중요한 시점에 수동으로 flush
|
|
446
|
-
|
|
447
|
-
await vircle.flush();
|
|
448
|
-
});
|
|
762
|
+
// 중요한 시점에 수동으로 flush (예: 결제 완료 후)
|
|
763
|
+
await vircle.flush();
|
|
449
764
|
```
|
|
450
765
|
|
|
451
766
|
### 민감한 정보가 있는 페이지
|
|
@@ -531,30 +846,51 @@ await vircle.flush();
|
|
|
531
846
|
### 메모리 누수 방지
|
|
532
847
|
|
|
533
848
|
```typescript
|
|
534
|
-
// 컴포넌트 언마운트 시 반드시 cleanup 호출
|
|
535
|
-
//
|
|
849
|
+
// React 예제 - 컴포넌트 언마운트 시 반드시 cleanup 호출
|
|
850
|
+
// 일반적으로 SDK는 앱 레벨에서 한 번만 초기화합니다
|
|
536
851
|
useEffect(() => {
|
|
537
|
-
const vircle = new VircleWeb({
|
|
852
|
+
const vircle = new VircleWeb({
|
|
853
|
+
apiKey: 'your-api-key',
|
|
854
|
+
trackPageViews: true,
|
|
855
|
+
});
|
|
538
856
|
vircle.initialize();
|
|
539
857
|
|
|
540
858
|
return () => {
|
|
859
|
+
// cleanup()은 이벤트 리스너 제거, History API 원복, 큐 플러시를 수행합니다
|
|
541
860
|
vircle.cleanup();
|
|
542
861
|
};
|
|
543
862
|
}, []);
|
|
544
863
|
```
|
|
545
864
|
|
|
865
|
+
### 암호화 관련
|
|
866
|
+
|
|
867
|
+
```typescript
|
|
868
|
+
// 암호화 미지원 환경에서 enableEncryption: true로 설정한 경우
|
|
869
|
+
// 콘솔에 경고 메시지가 출력되고 암호화 없이 동작합니다
|
|
870
|
+
// "[Vircle] Web Crypto API를 사용할 수 없습니다. 암호화를 비활성화합니다."
|
|
871
|
+
```
|
|
872
|
+
|
|
546
873
|
## TypeScript 지원
|
|
547
874
|
|
|
548
875
|
SDK는 완전한 TypeScript 타입 정의를 제공합니다:
|
|
549
876
|
|
|
550
877
|
```typescript
|
|
551
|
-
import {
|
|
878
|
+
import {
|
|
879
|
+
VircleWeb,
|
|
880
|
+
VircleWebConfig,
|
|
881
|
+
VircleWebOptions,
|
|
882
|
+
WebExtendedCryptoAdapter,
|
|
883
|
+
VircleWebError,
|
|
884
|
+
} from '@vircle/sdk-web';
|
|
885
|
+
import type { EventContext, UserTraits, StorageType } from '@vircle/sdk-web';
|
|
552
886
|
|
|
553
887
|
// 타입 안전한 설정
|
|
554
888
|
const config: VircleWebConfig = {
|
|
555
889
|
apiKey: process.env.VIRCLE_API_KEY!,
|
|
556
890
|
trackPageViews: true,
|
|
557
891
|
trackErrors: true,
|
|
892
|
+
enableEncryption: true,
|
|
893
|
+
storageType: 'auto',
|
|
558
894
|
};
|
|
559
895
|
|
|
560
896
|
const options: VircleWebOptions = {
|
|
@@ -565,14 +901,15 @@ const options: VircleWebOptions = {
|
|
|
565
901
|
const vircle = new VircleWeb(config, options);
|
|
566
902
|
|
|
567
903
|
// 타입 안전한 이벤트 추적
|
|
568
|
-
|
|
904
|
+
// track()의 제네릭 파라미터는 Record<string, unknown>을 확장해야 합니다
|
|
905
|
+
interface PurchaseProperties {
|
|
569
906
|
product_id: string;
|
|
570
907
|
price: number;
|
|
571
908
|
currency: string;
|
|
572
909
|
quantity: number;
|
|
573
910
|
}
|
|
574
911
|
|
|
575
|
-
await vircle.track<
|
|
912
|
+
await vircle.track<PurchaseProperties>('purchase', {
|
|
576
913
|
product_id: 'sku_123',
|
|
577
914
|
price: 49.99,
|
|
578
915
|
currency: 'USD',
|
|
@@ -580,13 +917,14 @@ await vircle.track<PurchaseEvent>('purchase', {
|
|
|
580
917
|
});
|
|
581
918
|
|
|
582
919
|
// 타입 안전한 사용자 속성
|
|
583
|
-
|
|
920
|
+
// identify()의 제네릭 파라미터는 Record<string, unknown>을 확장해야 합니다
|
|
921
|
+
interface CustomUserTraits {
|
|
584
922
|
email: string;
|
|
585
923
|
plan: 'free' | 'pro' | 'enterprise';
|
|
586
924
|
created_at: string;
|
|
587
925
|
}
|
|
588
926
|
|
|
589
|
-
await vircle.identify<
|
|
927
|
+
await vircle.identify<CustomUserTraits>('user-123', {
|
|
590
928
|
email: 'user@example.com',
|
|
591
929
|
plan: 'pro',
|
|
592
930
|
created_at: new Date().toISOString(),
|
|
@@ -595,4 +933,4 @@ await vircle.identify<UserTraits>('user-123', {
|
|
|
595
933
|
|
|
596
934
|
## 라이선스
|
|
597
935
|
|
|
598
|
-
MIT
|
|
936
|
+
MIT
|