connectbase-client 3.3.1 → 3.4.0
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/dist/connect-base.umd.js +3 -3
- package/dist/index.d.mts +47 -2
- package/dist/index.d.ts +47 -2
- package/dist/index.js +93 -5
- package/dist/index.mjs +93 -5
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -314,6 +314,18 @@ declare class SessionManager {
|
|
|
314
314
|
touch(): void;
|
|
315
315
|
/** 세션 강제 리셋 */
|
|
316
316
|
reset(): void;
|
|
317
|
+
/**
|
|
318
|
+
* visitor_uid 를 새로 발급하고 세션도 함께 초기화.
|
|
319
|
+
*
|
|
320
|
+
* 사용 시점:
|
|
321
|
+
* - 사용자 로그아웃 (이전 사용자 활동이 다음 사용자에 attribution 되는 것 방지)
|
|
322
|
+
* - 다른 사용자 로그인 감지 (link-member 가 `VISITOR_LINKED_TO_OTHER_MEMBER` 응답)
|
|
323
|
+
*
|
|
324
|
+
* localStorage 의 `__cb_visitor_uid` 가 새 UUID 로 교체되며 sessionStorage 의
|
|
325
|
+
* 세션 키도 같이 비워진다. 이후의 모든 batch 는 새 visitor 로 기록되어 멤버 간
|
|
326
|
+
* 데이터 오염이 차단된다.
|
|
327
|
+
*/
|
|
328
|
+
regenerateVisitorUid(): string;
|
|
317
329
|
private ensureSession;
|
|
318
330
|
private loadOrCreateVisitorUid;
|
|
319
331
|
}
|
|
@@ -368,6 +380,11 @@ declare class AnalyticsAPI {
|
|
|
368
380
|
* 백엔드 `link-member` 엔드포인트를 한 번 호출합니다 (1.11.0+). 호출 실패는
|
|
369
381
|
* silent — 다음 batch 가 닿을 때 백엔드 자동 매핑이 동일하게 처리하므로 자가 복구.
|
|
370
382
|
*
|
|
383
|
+
* **사용자 전환 자동 처리 (1.13.0+)**: 동일 브라우저에서 다른 사용자가 로그인해
|
|
384
|
+
* 백엔드가 `VISITOR_LINKED_TO_OTHER_MEMBER` 응답을 보내면, 큐를 비우고
|
|
385
|
+
* `visitor_uid` 를 새로 발급한 뒤 link-member 를 한 번 더 호출해 새 visitor 가
|
|
386
|
+
* 즉시 회원으로 기록되도록 자가 복구한다.
|
|
387
|
+
*
|
|
371
388
|
* 산업 표준(GA4 User-ID, Mixpanel/PostHog `identify`) 과 동작 정합.
|
|
372
389
|
*
|
|
373
390
|
* @example
|
|
@@ -378,11 +395,30 @@ declare class AnalyticsAPI {
|
|
|
378
395
|
* ```
|
|
379
396
|
*/
|
|
380
397
|
identify(memberId: string): void;
|
|
398
|
+
/**
|
|
399
|
+
* 로그아웃 / 사용자 전환 시 호출. 익명 상태로 복귀하면서 visitor_uid 를 새로
|
|
400
|
+
* 발급해 다음 사용자의 활동이 이전 사용자에게 attribution 되는 데이터 오염을 차단.
|
|
401
|
+
*
|
|
402
|
+
* 동작:
|
|
403
|
+
* 1. memberId 를 null 로 설정 (이후 batch 는 익명으로 기록)
|
|
404
|
+
* 2. 큐에 쌓인 미전송 이벤트 폐기 (이전 visitor 로 가는 것을 막기 위함)
|
|
405
|
+
* 3. localStorage `__cb_visitor_uid` 새 UUID 로 교체 + sessionStorage 세션 키 정리
|
|
406
|
+
* 4. heatmap 큐도 함께 비움
|
|
407
|
+
*
|
|
408
|
+
* @example
|
|
409
|
+
* ```ts
|
|
410
|
+
* // 로그아웃 핸들러
|
|
411
|
+
* await cb.auth.signOut()
|
|
412
|
+
* cb.analytics.reset()
|
|
413
|
+
* ```
|
|
414
|
+
*/
|
|
415
|
+
reset(): void;
|
|
381
416
|
/**
|
|
382
417
|
* 방문자 트래커에 현재 회원 ID 설정 (로그인/게스트 가입 시 호출).
|
|
383
418
|
*
|
|
384
419
|
* `identify()` 와 달리 즉시 backfill 호출은 하지 않습니다 — 단순히 이후 batch 의
|
|
385
|
-
* `app_member_id` 값만 갱신. null 을 넘기면 익명 상태로 복귀 (로그아웃
|
|
420
|
+
* `app_member_id` 값만 갱신. null 을 넘기면 익명 상태로 복귀 (로그아웃 시에는
|
|
421
|
+
* 데이터 오염 방지를 위해 `reset()` 를 권장).
|
|
386
422
|
*/
|
|
387
423
|
setMemberId(memberId: string | null): void;
|
|
388
424
|
/**
|
|
@@ -390,6 +426,9 @@ declare class AnalyticsAPI {
|
|
|
390
426
|
*
|
|
391
427
|
* - 첫 페이지뷰가 아직 백엔드에 닿기 전이면 visitor 가 없어 404. 무시 — 다음 batch 가
|
|
392
428
|
* 가면 백엔드 BatchRecordVisit 가 자동 LinkMember 를 호출.
|
|
429
|
+
* - 다른 멤버에 이미 연결된 visitor 인 경우 (`VISITOR_LINKED_TO_OTHER_MEMBER`)
|
|
430
|
+
* visitor_uid 를 자동 재발급하고 link-member 를 한 번 더 호출 — 한 단계의 자가
|
|
431
|
+
* 복구로 끝나며 무한 재귀를 막기 위해 두 번째 시도는 응답을 더 보지 않는다.
|
|
393
432
|
* - storage_web_id 가 init 안 됐거나 모든 종류의 네트워크 오류 — silent fail.
|
|
394
433
|
*/
|
|
395
434
|
private linkMemberSilent;
|
|
@@ -510,7 +549,13 @@ declare class AnalyticsAPI {
|
|
|
510
549
|
private createBaseEvent;
|
|
511
550
|
private enqueue;
|
|
512
551
|
private flushQueue;
|
|
513
|
-
/**
|
|
552
|
+
/**
|
|
553
|
+
* sendBeacon 으로 동기 flush (beforeunload 용).
|
|
554
|
+
*
|
|
555
|
+
* sendBeacon 은 일반 fetch 와 달리 User-Agent 헤더가 신뢰할 수 있지만, 백엔드 봇
|
|
556
|
+
* 탐지가 body 의 `user_agent` 필드 첫 번째 값만 보므로 여기서도 동일하게 채워
|
|
557
|
+
* 첫 방문이 unload 타이밍에 도달했을 때 봇으로 오판되는 것을 방지한다.
|
|
558
|
+
*/
|
|
514
559
|
private flushSync;
|
|
515
560
|
private trackSessionStart;
|
|
516
561
|
private startBatchTimer;
|
package/dist/index.d.ts
CHANGED
|
@@ -314,6 +314,18 @@ declare class SessionManager {
|
|
|
314
314
|
touch(): void;
|
|
315
315
|
/** 세션 강제 리셋 */
|
|
316
316
|
reset(): void;
|
|
317
|
+
/**
|
|
318
|
+
* visitor_uid 를 새로 발급하고 세션도 함께 초기화.
|
|
319
|
+
*
|
|
320
|
+
* 사용 시점:
|
|
321
|
+
* - 사용자 로그아웃 (이전 사용자 활동이 다음 사용자에 attribution 되는 것 방지)
|
|
322
|
+
* - 다른 사용자 로그인 감지 (link-member 가 `VISITOR_LINKED_TO_OTHER_MEMBER` 응답)
|
|
323
|
+
*
|
|
324
|
+
* localStorage 의 `__cb_visitor_uid` 가 새 UUID 로 교체되며 sessionStorage 의
|
|
325
|
+
* 세션 키도 같이 비워진다. 이후의 모든 batch 는 새 visitor 로 기록되어 멤버 간
|
|
326
|
+
* 데이터 오염이 차단된다.
|
|
327
|
+
*/
|
|
328
|
+
regenerateVisitorUid(): string;
|
|
317
329
|
private ensureSession;
|
|
318
330
|
private loadOrCreateVisitorUid;
|
|
319
331
|
}
|
|
@@ -368,6 +380,11 @@ declare class AnalyticsAPI {
|
|
|
368
380
|
* 백엔드 `link-member` 엔드포인트를 한 번 호출합니다 (1.11.0+). 호출 실패는
|
|
369
381
|
* silent — 다음 batch 가 닿을 때 백엔드 자동 매핑이 동일하게 처리하므로 자가 복구.
|
|
370
382
|
*
|
|
383
|
+
* **사용자 전환 자동 처리 (1.13.0+)**: 동일 브라우저에서 다른 사용자가 로그인해
|
|
384
|
+
* 백엔드가 `VISITOR_LINKED_TO_OTHER_MEMBER` 응답을 보내면, 큐를 비우고
|
|
385
|
+
* `visitor_uid` 를 새로 발급한 뒤 link-member 를 한 번 더 호출해 새 visitor 가
|
|
386
|
+
* 즉시 회원으로 기록되도록 자가 복구한다.
|
|
387
|
+
*
|
|
371
388
|
* 산업 표준(GA4 User-ID, Mixpanel/PostHog `identify`) 과 동작 정합.
|
|
372
389
|
*
|
|
373
390
|
* @example
|
|
@@ -378,11 +395,30 @@ declare class AnalyticsAPI {
|
|
|
378
395
|
* ```
|
|
379
396
|
*/
|
|
380
397
|
identify(memberId: string): void;
|
|
398
|
+
/**
|
|
399
|
+
* 로그아웃 / 사용자 전환 시 호출. 익명 상태로 복귀하면서 visitor_uid 를 새로
|
|
400
|
+
* 발급해 다음 사용자의 활동이 이전 사용자에게 attribution 되는 데이터 오염을 차단.
|
|
401
|
+
*
|
|
402
|
+
* 동작:
|
|
403
|
+
* 1. memberId 를 null 로 설정 (이후 batch 는 익명으로 기록)
|
|
404
|
+
* 2. 큐에 쌓인 미전송 이벤트 폐기 (이전 visitor 로 가는 것을 막기 위함)
|
|
405
|
+
* 3. localStorage `__cb_visitor_uid` 새 UUID 로 교체 + sessionStorage 세션 키 정리
|
|
406
|
+
* 4. heatmap 큐도 함께 비움
|
|
407
|
+
*
|
|
408
|
+
* @example
|
|
409
|
+
* ```ts
|
|
410
|
+
* // 로그아웃 핸들러
|
|
411
|
+
* await cb.auth.signOut()
|
|
412
|
+
* cb.analytics.reset()
|
|
413
|
+
* ```
|
|
414
|
+
*/
|
|
415
|
+
reset(): void;
|
|
381
416
|
/**
|
|
382
417
|
* 방문자 트래커에 현재 회원 ID 설정 (로그인/게스트 가입 시 호출).
|
|
383
418
|
*
|
|
384
419
|
* `identify()` 와 달리 즉시 backfill 호출은 하지 않습니다 — 단순히 이후 batch 의
|
|
385
|
-
* `app_member_id` 값만 갱신. null 을 넘기면 익명 상태로 복귀 (로그아웃
|
|
420
|
+
* `app_member_id` 값만 갱신. null 을 넘기면 익명 상태로 복귀 (로그아웃 시에는
|
|
421
|
+
* 데이터 오염 방지를 위해 `reset()` 를 권장).
|
|
386
422
|
*/
|
|
387
423
|
setMemberId(memberId: string | null): void;
|
|
388
424
|
/**
|
|
@@ -390,6 +426,9 @@ declare class AnalyticsAPI {
|
|
|
390
426
|
*
|
|
391
427
|
* - 첫 페이지뷰가 아직 백엔드에 닿기 전이면 visitor 가 없어 404. 무시 — 다음 batch 가
|
|
392
428
|
* 가면 백엔드 BatchRecordVisit 가 자동 LinkMember 를 호출.
|
|
429
|
+
* - 다른 멤버에 이미 연결된 visitor 인 경우 (`VISITOR_LINKED_TO_OTHER_MEMBER`)
|
|
430
|
+
* visitor_uid 를 자동 재발급하고 link-member 를 한 번 더 호출 — 한 단계의 자가
|
|
431
|
+
* 복구로 끝나며 무한 재귀를 막기 위해 두 번째 시도는 응답을 더 보지 않는다.
|
|
393
432
|
* - storage_web_id 가 init 안 됐거나 모든 종류의 네트워크 오류 — silent fail.
|
|
394
433
|
*/
|
|
395
434
|
private linkMemberSilent;
|
|
@@ -510,7 +549,13 @@ declare class AnalyticsAPI {
|
|
|
510
549
|
private createBaseEvent;
|
|
511
550
|
private enqueue;
|
|
512
551
|
private flushQueue;
|
|
513
|
-
/**
|
|
552
|
+
/**
|
|
553
|
+
* sendBeacon 으로 동기 flush (beforeunload 용).
|
|
554
|
+
*
|
|
555
|
+
* sendBeacon 은 일반 fetch 와 달리 User-Agent 헤더가 신뢰할 수 있지만, 백엔드 봇
|
|
556
|
+
* 탐지가 body 의 `user_agent` 필드 첫 번째 값만 보므로 여기서도 동일하게 채워
|
|
557
|
+
* 첫 방문이 unload 타이밍에 도달했을 때 봇으로 오판되는 것을 방지한다.
|
|
558
|
+
*/
|
|
514
559
|
private flushSync;
|
|
515
560
|
private trackSessionStart;
|
|
516
561
|
private startBatchTimer;
|
package/dist/index.js
CHANGED
|
@@ -8034,6 +8034,29 @@ var SessionManager = class {
|
|
|
8034
8034
|
} catch {
|
|
8035
8035
|
}
|
|
8036
8036
|
}
|
|
8037
|
+
/**
|
|
8038
|
+
* visitor_uid 를 새로 발급하고 세션도 함께 초기화.
|
|
8039
|
+
*
|
|
8040
|
+
* 사용 시점:
|
|
8041
|
+
* - 사용자 로그아웃 (이전 사용자 활동이 다음 사용자에 attribution 되는 것 방지)
|
|
8042
|
+
* - 다른 사용자 로그인 감지 (link-member 가 `VISITOR_LINKED_TO_OTHER_MEMBER` 응답)
|
|
8043
|
+
*
|
|
8044
|
+
* localStorage 의 `__cb_visitor_uid` 가 새 UUID 로 교체되며 sessionStorage 의
|
|
8045
|
+
* 세션 키도 같이 비워진다. 이후의 모든 batch 는 새 visitor 로 기록되어 멤버 간
|
|
8046
|
+
* 데이터 오염이 차단된다.
|
|
8047
|
+
*/
|
|
8048
|
+
regenerateVisitorUid() {
|
|
8049
|
+
const newUid = generateId();
|
|
8050
|
+
this._visitorUid = newUid;
|
|
8051
|
+
try {
|
|
8052
|
+
if (typeof localStorage !== "undefined") {
|
|
8053
|
+
localStorage.setItem(VISITOR_KEY, newUid);
|
|
8054
|
+
}
|
|
8055
|
+
} catch {
|
|
8056
|
+
}
|
|
8057
|
+
this.reset();
|
|
8058
|
+
return newUid;
|
|
8059
|
+
}
|
|
8037
8060
|
ensureSession() {
|
|
8038
8061
|
const now = Date.now();
|
|
8039
8062
|
if (!this._sessionId) {
|
|
@@ -8239,6 +8262,11 @@ var AnalyticsAPI = class {
|
|
|
8239
8262
|
* 백엔드 `link-member` 엔드포인트를 한 번 호출합니다 (1.11.0+). 호출 실패는
|
|
8240
8263
|
* silent — 다음 batch 가 닿을 때 백엔드 자동 매핑이 동일하게 처리하므로 자가 복구.
|
|
8241
8264
|
*
|
|
8265
|
+
* **사용자 전환 자동 처리 (1.13.0+)**: 동일 브라우저에서 다른 사용자가 로그인해
|
|
8266
|
+
* 백엔드가 `VISITOR_LINKED_TO_OTHER_MEMBER` 응답을 보내면, 큐를 비우고
|
|
8267
|
+
* `visitor_uid` 를 새로 발급한 뒤 link-member 를 한 번 더 호출해 새 visitor 가
|
|
8268
|
+
* 즉시 회원으로 기록되도록 자가 복구한다.
|
|
8269
|
+
*
|
|
8242
8270
|
* 산업 표준(GA4 User-ID, Mixpanel/PostHog `identify`) 과 동작 정합.
|
|
8243
8271
|
*
|
|
8244
8272
|
* @example
|
|
@@ -8249,14 +8277,42 @@ var AnalyticsAPI = class {
|
|
|
8249
8277
|
* ```
|
|
8250
8278
|
*/
|
|
8251
8279
|
identify(memberId) {
|
|
8280
|
+
if (this.memberId && this.memberId !== memberId) {
|
|
8281
|
+
this.reset();
|
|
8282
|
+
}
|
|
8252
8283
|
this.setMemberId(memberId);
|
|
8253
8284
|
this.linkMemberSilent(memberId);
|
|
8254
8285
|
}
|
|
8286
|
+
/**
|
|
8287
|
+
* 로그아웃 / 사용자 전환 시 호출. 익명 상태로 복귀하면서 visitor_uid 를 새로
|
|
8288
|
+
* 발급해 다음 사용자의 활동이 이전 사용자에게 attribution 되는 데이터 오염을 차단.
|
|
8289
|
+
*
|
|
8290
|
+
* 동작:
|
|
8291
|
+
* 1. memberId 를 null 로 설정 (이후 batch 는 익명으로 기록)
|
|
8292
|
+
* 2. 큐에 쌓인 미전송 이벤트 폐기 (이전 visitor 로 가는 것을 막기 위함)
|
|
8293
|
+
* 3. localStorage `__cb_visitor_uid` 새 UUID 로 교체 + sessionStorage 세션 키 정리
|
|
8294
|
+
* 4. heatmap 큐도 함께 비움
|
|
8295
|
+
*
|
|
8296
|
+
* @example
|
|
8297
|
+
* ```ts
|
|
8298
|
+
* // 로그아웃 핸들러
|
|
8299
|
+
* await cb.auth.signOut()
|
|
8300
|
+
* cb.analytics.reset()
|
|
8301
|
+
* ```
|
|
8302
|
+
*/
|
|
8303
|
+
reset() {
|
|
8304
|
+
this.setMemberId(null);
|
|
8305
|
+
this.eventQueue = [];
|
|
8306
|
+
this.heatmapQueue = [];
|
|
8307
|
+
const newUid = this.session.regenerateVisitorUid();
|
|
8308
|
+
this.log("Analytics reset", { newVisitorUid: newUid });
|
|
8309
|
+
}
|
|
8255
8310
|
/**
|
|
8256
8311
|
* 방문자 트래커에 현재 회원 ID 설정 (로그인/게스트 가입 시 호출).
|
|
8257
8312
|
*
|
|
8258
8313
|
* `identify()` 와 달리 즉시 backfill 호출은 하지 않습니다 — 단순히 이후 batch 의
|
|
8259
|
-
* `app_member_id` 값만 갱신. null 을 넘기면 익명 상태로 복귀 (로그아웃
|
|
8314
|
+
* `app_member_id` 값만 갱신. null 을 넘기면 익명 상태로 복귀 (로그아웃 시에는
|
|
8315
|
+
* 데이터 오염 방지를 위해 `reset()` 를 권장).
|
|
8260
8316
|
*/
|
|
8261
8317
|
setMemberId(memberId) {
|
|
8262
8318
|
this.memberId = memberId || null;
|
|
@@ -8278,9 +8334,12 @@ var AnalyticsAPI = class {
|
|
|
8278
8334
|
*
|
|
8279
8335
|
* - 첫 페이지뷰가 아직 백엔드에 닿기 전이면 visitor 가 없어 404. 무시 — 다음 batch 가
|
|
8280
8336
|
* 가면 백엔드 BatchRecordVisit 가 자동 LinkMember 를 호출.
|
|
8337
|
+
* - 다른 멤버에 이미 연결된 visitor 인 경우 (`VISITOR_LINKED_TO_OTHER_MEMBER`)
|
|
8338
|
+
* visitor_uid 를 자동 재발급하고 link-member 를 한 번 더 호출 — 한 단계의 자가
|
|
8339
|
+
* 복구로 끝나며 무한 재귀를 막기 위해 두 번째 시도는 응답을 더 보지 않는다.
|
|
8281
8340
|
* - storage_web_id 가 init 안 됐거나 모든 종류의 네트워크 오류 — silent fail.
|
|
8282
8341
|
*/
|
|
8283
|
-
linkMemberSilent(memberId) {
|
|
8342
|
+
linkMemberSilent(memberId, isRetry = false) {
|
|
8284
8343
|
if (!this.storageWebId) return;
|
|
8285
8344
|
const prefix = this.http.hasPublicKey() ? "/v1/public" : "/v1";
|
|
8286
8345
|
this.http.post(
|
|
@@ -8289,7 +8348,18 @@ var AnalyticsAPI = class {
|
|
|
8289
8348
|
visitor_uid: this.session.visitorUid,
|
|
8290
8349
|
app_member_id: memberId
|
|
8291
8350
|
}
|
|
8292
|
-
).
|
|
8351
|
+
).then((resp) => {
|
|
8352
|
+
if (!resp) return;
|
|
8353
|
+
this.log("link-member response", resp);
|
|
8354
|
+
if (!isRetry && resp.success === false && resp.code === "VISITOR_LINKED_TO_OTHER_MEMBER") {
|
|
8355
|
+
this.log("user-switch detected \u2014 regenerating visitor_uid");
|
|
8356
|
+
this.eventQueue = [];
|
|
8357
|
+
this.heatmapQueue = [];
|
|
8358
|
+
this.session.regenerateVisitorUid();
|
|
8359
|
+
this.linkMemberSilent(memberId, true);
|
|
8360
|
+
}
|
|
8361
|
+
}).catch((err) => {
|
|
8362
|
+
this.log("link-member silent fail", err);
|
|
8293
8363
|
});
|
|
8294
8364
|
}
|
|
8295
8365
|
/** 현재 설정된 회원 ID 조회 (미설정 시 null) */
|
|
@@ -8523,6 +8593,7 @@ var AnalyticsAPI = class {
|
|
|
8523
8593
|
createBaseEvent(type) {
|
|
8524
8594
|
return {
|
|
8525
8595
|
type,
|
|
8596
|
+
event_id: generateId(),
|
|
8526
8597
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8527
8598
|
session_id: this.session.sessionId,
|
|
8528
8599
|
visitor_uid: this.session.visitorUid
|
|
@@ -8545,6 +8616,7 @@ var AnalyticsAPI = class {
|
|
|
8545
8616
|
visitor_uid: this.session.visitorUid,
|
|
8546
8617
|
...this.memberId ? { app_member_id: this.memberId } : {},
|
|
8547
8618
|
events: events.map((e) => ({
|
|
8619
|
+
event_id: e.event_id,
|
|
8548
8620
|
timestamp: e.timestamp,
|
|
8549
8621
|
page_path: e.page_path || "",
|
|
8550
8622
|
page_url: e.page_url || "",
|
|
@@ -8574,7 +8646,13 @@ var AnalyticsAPI = class {
|
|
|
8574
8646
|
this.log("Flush failed", err);
|
|
8575
8647
|
}
|
|
8576
8648
|
}
|
|
8577
|
-
/**
|
|
8649
|
+
/**
|
|
8650
|
+
* sendBeacon 으로 동기 flush (beforeunload 용).
|
|
8651
|
+
*
|
|
8652
|
+
* sendBeacon 은 일반 fetch 와 달리 User-Agent 헤더가 신뢰할 수 있지만, 백엔드 봇
|
|
8653
|
+
* 탐지가 body 의 `user_agent` 필드 첫 번째 값만 보므로 여기서도 동일하게 채워
|
|
8654
|
+
* 첫 방문이 unload 타이밍에 도달했을 때 봇으로 오판되는 것을 방지한다.
|
|
8655
|
+
*/
|
|
8578
8656
|
flushSync() {
|
|
8579
8657
|
if (this.eventQueue.length === 0 || !this.storageWebId) return;
|
|
8580
8658
|
if (typeof navigator === "undefined" || !navigator.sendBeacon) return;
|
|
@@ -8582,20 +8660,30 @@ var AnalyticsAPI = class {
|
|
|
8582
8660
|
const prefix = this.http.hasPublicKey() ? "/v1/public" : "/v1";
|
|
8583
8661
|
const baseUrl = this.http.getBaseUrl();
|
|
8584
8662
|
const url = `${baseUrl}${prefix}/storages/web/${this.storageWebId}/visitors/batch`;
|
|
8663
|
+
const ua = typeof navigator !== "undefined" ? navigator.userAgent : "";
|
|
8585
8664
|
const body = JSON.stringify({
|
|
8586
8665
|
visitor_uid: this.session.visitorUid,
|
|
8587
8666
|
...this.memberId ? { app_member_id: this.memberId } : {},
|
|
8588
8667
|
events: events.map((e) => ({
|
|
8668
|
+
event_id: e.event_id,
|
|
8589
8669
|
timestamp: e.timestamp,
|
|
8590
8670
|
page_path: e.page_path || "",
|
|
8591
8671
|
page_url: e.page_url || "",
|
|
8592
8672
|
page_title: e.page_title || "",
|
|
8593
8673
|
referrer: e.referrer || "",
|
|
8674
|
+
user_agent: ua,
|
|
8675
|
+
screen_width: e.screen_width || 0,
|
|
8676
|
+
screen_height: e.screen_height || 0,
|
|
8594
8677
|
session_id: e.session_id,
|
|
8595
8678
|
session_start: e.type === "session_start",
|
|
8596
8679
|
is_page_view: e.type === "page_view",
|
|
8597
8680
|
event_name: e.event_name,
|
|
8598
|
-
event_properties: e.event_properties
|
|
8681
|
+
event_properties: e.event_properties,
|
|
8682
|
+
utm_source: e.utm_source,
|
|
8683
|
+
utm_medium: e.utm_medium,
|
|
8684
|
+
utm_campaign: e.utm_campaign,
|
|
8685
|
+
utm_content: e.utm_content,
|
|
8686
|
+
utm_term: e.utm_term
|
|
8599
8687
|
}))
|
|
8600
8688
|
});
|
|
8601
8689
|
try {
|
package/dist/index.mjs
CHANGED
|
@@ -7994,6 +7994,29 @@ var SessionManager = class {
|
|
|
7994
7994
|
} catch {
|
|
7995
7995
|
}
|
|
7996
7996
|
}
|
|
7997
|
+
/**
|
|
7998
|
+
* visitor_uid 를 새로 발급하고 세션도 함께 초기화.
|
|
7999
|
+
*
|
|
8000
|
+
* 사용 시점:
|
|
8001
|
+
* - 사용자 로그아웃 (이전 사용자 활동이 다음 사용자에 attribution 되는 것 방지)
|
|
8002
|
+
* - 다른 사용자 로그인 감지 (link-member 가 `VISITOR_LINKED_TO_OTHER_MEMBER` 응답)
|
|
8003
|
+
*
|
|
8004
|
+
* localStorage 의 `__cb_visitor_uid` 가 새 UUID 로 교체되며 sessionStorage 의
|
|
8005
|
+
* 세션 키도 같이 비워진다. 이후의 모든 batch 는 새 visitor 로 기록되어 멤버 간
|
|
8006
|
+
* 데이터 오염이 차단된다.
|
|
8007
|
+
*/
|
|
8008
|
+
regenerateVisitorUid() {
|
|
8009
|
+
const newUid = generateId();
|
|
8010
|
+
this._visitorUid = newUid;
|
|
8011
|
+
try {
|
|
8012
|
+
if (typeof localStorage !== "undefined") {
|
|
8013
|
+
localStorage.setItem(VISITOR_KEY, newUid);
|
|
8014
|
+
}
|
|
8015
|
+
} catch {
|
|
8016
|
+
}
|
|
8017
|
+
this.reset();
|
|
8018
|
+
return newUid;
|
|
8019
|
+
}
|
|
7997
8020
|
ensureSession() {
|
|
7998
8021
|
const now = Date.now();
|
|
7999
8022
|
if (!this._sessionId) {
|
|
@@ -8199,6 +8222,11 @@ var AnalyticsAPI = class {
|
|
|
8199
8222
|
* 백엔드 `link-member` 엔드포인트를 한 번 호출합니다 (1.11.0+). 호출 실패는
|
|
8200
8223
|
* silent — 다음 batch 가 닿을 때 백엔드 자동 매핑이 동일하게 처리하므로 자가 복구.
|
|
8201
8224
|
*
|
|
8225
|
+
* **사용자 전환 자동 처리 (1.13.0+)**: 동일 브라우저에서 다른 사용자가 로그인해
|
|
8226
|
+
* 백엔드가 `VISITOR_LINKED_TO_OTHER_MEMBER` 응답을 보내면, 큐를 비우고
|
|
8227
|
+
* `visitor_uid` 를 새로 발급한 뒤 link-member 를 한 번 더 호출해 새 visitor 가
|
|
8228
|
+
* 즉시 회원으로 기록되도록 자가 복구한다.
|
|
8229
|
+
*
|
|
8202
8230
|
* 산업 표준(GA4 User-ID, Mixpanel/PostHog `identify`) 과 동작 정합.
|
|
8203
8231
|
*
|
|
8204
8232
|
* @example
|
|
@@ -8209,14 +8237,42 @@ var AnalyticsAPI = class {
|
|
|
8209
8237
|
* ```
|
|
8210
8238
|
*/
|
|
8211
8239
|
identify(memberId) {
|
|
8240
|
+
if (this.memberId && this.memberId !== memberId) {
|
|
8241
|
+
this.reset();
|
|
8242
|
+
}
|
|
8212
8243
|
this.setMemberId(memberId);
|
|
8213
8244
|
this.linkMemberSilent(memberId);
|
|
8214
8245
|
}
|
|
8246
|
+
/**
|
|
8247
|
+
* 로그아웃 / 사용자 전환 시 호출. 익명 상태로 복귀하면서 visitor_uid 를 새로
|
|
8248
|
+
* 발급해 다음 사용자의 활동이 이전 사용자에게 attribution 되는 데이터 오염을 차단.
|
|
8249
|
+
*
|
|
8250
|
+
* 동작:
|
|
8251
|
+
* 1. memberId 를 null 로 설정 (이후 batch 는 익명으로 기록)
|
|
8252
|
+
* 2. 큐에 쌓인 미전송 이벤트 폐기 (이전 visitor 로 가는 것을 막기 위함)
|
|
8253
|
+
* 3. localStorage `__cb_visitor_uid` 새 UUID 로 교체 + sessionStorage 세션 키 정리
|
|
8254
|
+
* 4. heatmap 큐도 함께 비움
|
|
8255
|
+
*
|
|
8256
|
+
* @example
|
|
8257
|
+
* ```ts
|
|
8258
|
+
* // 로그아웃 핸들러
|
|
8259
|
+
* await cb.auth.signOut()
|
|
8260
|
+
* cb.analytics.reset()
|
|
8261
|
+
* ```
|
|
8262
|
+
*/
|
|
8263
|
+
reset() {
|
|
8264
|
+
this.setMemberId(null);
|
|
8265
|
+
this.eventQueue = [];
|
|
8266
|
+
this.heatmapQueue = [];
|
|
8267
|
+
const newUid = this.session.regenerateVisitorUid();
|
|
8268
|
+
this.log("Analytics reset", { newVisitorUid: newUid });
|
|
8269
|
+
}
|
|
8215
8270
|
/**
|
|
8216
8271
|
* 방문자 트래커에 현재 회원 ID 설정 (로그인/게스트 가입 시 호출).
|
|
8217
8272
|
*
|
|
8218
8273
|
* `identify()` 와 달리 즉시 backfill 호출은 하지 않습니다 — 단순히 이후 batch 의
|
|
8219
|
-
* `app_member_id` 값만 갱신. null 을 넘기면 익명 상태로 복귀 (로그아웃
|
|
8274
|
+
* `app_member_id` 값만 갱신. null 을 넘기면 익명 상태로 복귀 (로그아웃 시에는
|
|
8275
|
+
* 데이터 오염 방지를 위해 `reset()` 를 권장).
|
|
8220
8276
|
*/
|
|
8221
8277
|
setMemberId(memberId) {
|
|
8222
8278
|
this.memberId = memberId || null;
|
|
@@ -8238,9 +8294,12 @@ var AnalyticsAPI = class {
|
|
|
8238
8294
|
*
|
|
8239
8295
|
* - 첫 페이지뷰가 아직 백엔드에 닿기 전이면 visitor 가 없어 404. 무시 — 다음 batch 가
|
|
8240
8296
|
* 가면 백엔드 BatchRecordVisit 가 자동 LinkMember 를 호출.
|
|
8297
|
+
* - 다른 멤버에 이미 연결된 visitor 인 경우 (`VISITOR_LINKED_TO_OTHER_MEMBER`)
|
|
8298
|
+
* visitor_uid 를 자동 재발급하고 link-member 를 한 번 더 호출 — 한 단계의 자가
|
|
8299
|
+
* 복구로 끝나며 무한 재귀를 막기 위해 두 번째 시도는 응답을 더 보지 않는다.
|
|
8241
8300
|
* - storage_web_id 가 init 안 됐거나 모든 종류의 네트워크 오류 — silent fail.
|
|
8242
8301
|
*/
|
|
8243
|
-
linkMemberSilent(memberId) {
|
|
8302
|
+
linkMemberSilent(memberId, isRetry = false) {
|
|
8244
8303
|
if (!this.storageWebId) return;
|
|
8245
8304
|
const prefix = this.http.hasPublicKey() ? "/v1/public" : "/v1";
|
|
8246
8305
|
this.http.post(
|
|
@@ -8249,7 +8308,18 @@ var AnalyticsAPI = class {
|
|
|
8249
8308
|
visitor_uid: this.session.visitorUid,
|
|
8250
8309
|
app_member_id: memberId
|
|
8251
8310
|
}
|
|
8252
|
-
).
|
|
8311
|
+
).then((resp) => {
|
|
8312
|
+
if (!resp) return;
|
|
8313
|
+
this.log("link-member response", resp);
|
|
8314
|
+
if (!isRetry && resp.success === false && resp.code === "VISITOR_LINKED_TO_OTHER_MEMBER") {
|
|
8315
|
+
this.log("user-switch detected \u2014 regenerating visitor_uid");
|
|
8316
|
+
this.eventQueue = [];
|
|
8317
|
+
this.heatmapQueue = [];
|
|
8318
|
+
this.session.regenerateVisitorUid();
|
|
8319
|
+
this.linkMemberSilent(memberId, true);
|
|
8320
|
+
}
|
|
8321
|
+
}).catch((err) => {
|
|
8322
|
+
this.log("link-member silent fail", err);
|
|
8253
8323
|
});
|
|
8254
8324
|
}
|
|
8255
8325
|
/** 현재 설정된 회원 ID 조회 (미설정 시 null) */
|
|
@@ -8483,6 +8553,7 @@ var AnalyticsAPI = class {
|
|
|
8483
8553
|
createBaseEvent(type) {
|
|
8484
8554
|
return {
|
|
8485
8555
|
type,
|
|
8556
|
+
event_id: generateId(),
|
|
8486
8557
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8487
8558
|
session_id: this.session.sessionId,
|
|
8488
8559
|
visitor_uid: this.session.visitorUid
|
|
@@ -8505,6 +8576,7 @@ var AnalyticsAPI = class {
|
|
|
8505
8576
|
visitor_uid: this.session.visitorUid,
|
|
8506
8577
|
...this.memberId ? { app_member_id: this.memberId } : {},
|
|
8507
8578
|
events: events.map((e) => ({
|
|
8579
|
+
event_id: e.event_id,
|
|
8508
8580
|
timestamp: e.timestamp,
|
|
8509
8581
|
page_path: e.page_path || "",
|
|
8510
8582
|
page_url: e.page_url || "",
|
|
@@ -8534,7 +8606,13 @@ var AnalyticsAPI = class {
|
|
|
8534
8606
|
this.log("Flush failed", err);
|
|
8535
8607
|
}
|
|
8536
8608
|
}
|
|
8537
|
-
/**
|
|
8609
|
+
/**
|
|
8610
|
+
* sendBeacon 으로 동기 flush (beforeunload 용).
|
|
8611
|
+
*
|
|
8612
|
+
* sendBeacon 은 일반 fetch 와 달리 User-Agent 헤더가 신뢰할 수 있지만, 백엔드 봇
|
|
8613
|
+
* 탐지가 body 의 `user_agent` 필드 첫 번째 값만 보므로 여기서도 동일하게 채워
|
|
8614
|
+
* 첫 방문이 unload 타이밍에 도달했을 때 봇으로 오판되는 것을 방지한다.
|
|
8615
|
+
*/
|
|
8538
8616
|
flushSync() {
|
|
8539
8617
|
if (this.eventQueue.length === 0 || !this.storageWebId) return;
|
|
8540
8618
|
if (typeof navigator === "undefined" || !navigator.sendBeacon) return;
|
|
@@ -8542,20 +8620,30 @@ var AnalyticsAPI = class {
|
|
|
8542
8620
|
const prefix = this.http.hasPublicKey() ? "/v1/public" : "/v1";
|
|
8543
8621
|
const baseUrl = this.http.getBaseUrl();
|
|
8544
8622
|
const url = `${baseUrl}${prefix}/storages/web/${this.storageWebId}/visitors/batch`;
|
|
8623
|
+
const ua = typeof navigator !== "undefined" ? navigator.userAgent : "";
|
|
8545
8624
|
const body = JSON.stringify({
|
|
8546
8625
|
visitor_uid: this.session.visitorUid,
|
|
8547
8626
|
...this.memberId ? { app_member_id: this.memberId } : {},
|
|
8548
8627
|
events: events.map((e) => ({
|
|
8628
|
+
event_id: e.event_id,
|
|
8549
8629
|
timestamp: e.timestamp,
|
|
8550
8630
|
page_path: e.page_path || "",
|
|
8551
8631
|
page_url: e.page_url || "",
|
|
8552
8632
|
page_title: e.page_title || "",
|
|
8553
8633
|
referrer: e.referrer || "",
|
|
8634
|
+
user_agent: ua,
|
|
8635
|
+
screen_width: e.screen_width || 0,
|
|
8636
|
+
screen_height: e.screen_height || 0,
|
|
8554
8637
|
session_id: e.session_id,
|
|
8555
8638
|
session_start: e.type === "session_start",
|
|
8556
8639
|
is_page_view: e.type === "page_view",
|
|
8557
8640
|
event_name: e.event_name,
|
|
8558
|
-
event_properties: e.event_properties
|
|
8641
|
+
event_properties: e.event_properties,
|
|
8642
|
+
utm_source: e.utm_source,
|
|
8643
|
+
utm_medium: e.utm_medium,
|
|
8644
|
+
utm_campaign: e.utm_campaign,
|
|
8645
|
+
utm_content: e.utm_content,
|
|
8646
|
+
utm_term: e.utm_term
|
|
8559
8647
|
}))
|
|
8560
8648
|
});
|
|
8561
8649
|
try {
|