connectbase-client 1.10.0 → 1.12.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/index.d.mts CHANGED
@@ -241,7 +241,66 @@ interface AnalyticsRangeOptions {
241
241
  interface VisitorListOptions {
242
242
  limit?: number;
243
243
  offset?: number;
244
- sort_by?: 'last_visit' | 'total_visits' | 'total_page_views';
244
+ /**
245
+ * 백엔드가 인식하는 정렬 키 — 외 값은 silent 로 default(`last_visit`) 처리됩니다.
246
+ *
247
+ * 1.12.0 에서 백엔드 실제 동작에 맞춰 enum 정정 (1.10/1.11 의 `total_visits`/
248
+ * `total_page_views` 는 백엔드에서 인식되지 않아 항상 default 분기였음).
249
+ */
250
+ sort_by?: 'last_visit' | 'visits' | 'page_views' | 'first_visit';
251
+ }
252
+ /**
253
+ * 멤버별 합산 방문자 그룹 항목.
254
+ *
255
+ * 한 명의 회원이 여러 디바이스/브라우저로 접속했을 때 visitor row 들을 합쳐 단일 row.
256
+ * 익명 visitor 는 `app_member_id == undefined` 로 단일 row 그대로 노출되며 `visitor_count == 1`.
257
+ *
258
+ * `visitor_count` 는 "디바이스 수" 가 아닌 **"추적 브라우저 인스턴스 수"** 를 의미합니다.
259
+ * 같은 디바이스에서 시크릿모드 + 일반모드는 visitor 2 = visitor_count 2 로 카운트됩니다.
260
+ */
261
+ interface VisitorGroupItem {
262
+ app_member_id?: string;
263
+ /** 익명 visitor row 의 경우 단일 visitor_uid. 회원 그룹은 visitor_uids 참조. */
264
+ visitor_uid?: string;
265
+ /** 회원 그룹에 속한 visitor_uid 목록. 익명 row 는 undefined. */
266
+ visitor_uids?: string[];
267
+ visitor_count: number;
268
+ total_visits: number;
269
+ total_page_views: number;
270
+ first_visit_at: string;
271
+ last_visit_at: string;
272
+ country?: string;
273
+ is_bot: boolean;
274
+ }
275
+ interface VisitorGroupListResponse {
276
+ groups: VisitorGroupItem[];
277
+ total: number;
278
+ limit: number;
279
+ offset: number;
280
+ has_more: boolean;
281
+ }
282
+ interface VisitorByMemberResponse {
283
+ app_member_id: string;
284
+ visitor_uids: string[];
285
+ visitor_count: number;
286
+ total_visits: number;
287
+ total_page_views: number;
288
+ first_visit_at: string;
289
+ last_visit_at: string;
290
+ country?: string;
291
+ is_bot: boolean;
292
+ }
293
+ interface MergeVisitorsRequest {
294
+ source_visitor_uid: string;
295
+ /** target_visitor_uid 또는 target_member_id 중 하나는 필수. */
296
+ target_visitor_uid?: string;
297
+ target_member_id?: string;
298
+ }
299
+ interface MergeVisitorsResponse {
300
+ success: boolean;
301
+ target_visitor_id: string;
302
+ moved_records: number;
303
+ message?: string;
245
304
  }
246
305
  declare class SessionManager {
247
306
  private _sessionId;
@@ -302,15 +361,38 @@ declare class AnalyticsAPI {
302
361
  */
303
362
  trackEvent(name: string, properties?: Record<string, unknown>): void;
304
363
  /**
305
- * 사용자 식별 (로그인 )
306
- * 이후 모든 방문 배치에 `app_member_id`가 첨부되어 게스트 방문자가 회원으로 연결됩니다.
364
+ * 사용자 식별 (로그인 직후 호출).
365
+ *
366
+ * 이후 모든 방문 배치에 `app_member_id` 가 첨부되어 새 활동은 회원으로 기록됩니다.
367
+ * 추가로 **현재 visitor_uid 의 기존 익명 활동을 즉시 회원에게 backfill** 하기 위해
368
+ * 백엔드 `link-member` 엔드포인트를 한 번 호출합니다 (1.11.0+). 호출 실패는
369
+ * silent — 다음 batch 가 닿을 때 백엔드 자동 매핑이 동일하게 처리하므로 자가 복구.
370
+ *
371
+ * 산업 표준(GA4 User-ID, Mixpanel/PostHog `identify`) 과 동작 정합.
372
+ *
373
+ * @example
374
+ * ```ts
375
+ * // 로그인 성공 직후
376
+ * await cb.auth.signIn({ email, password })
377
+ * cb.analytics.identify(member.id)
378
+ * ```
307
379
  */
308
380
  identify(memberId: string): void;
309
381
  /**
310
- * 방문자 트래커에 현재 회원 ID 설정 (로그인/게스트 가입 시 호출)
311
- * null 을 넘기면 익명 상태로 복귀 (로그아웃).
382
+ * 방문자 트래커에 현재 회원 ID 설정 (로그인/게스트 가입 시 호출).
383
+ *
384
+ * `identify()` 와 달리 즉시 backfill 호출은 하지 않습니다 — 단순히 이후 batch 의
385
+ * `app_member_id` 값만 갱신. null 을 넘기면 익명 상태로 복귀 (로그아웃).
312
386
  */
313
387
  setMemberId(memberId: string | null): void;
388
+ /**
389
+ * 백엔드 link-member 엔드포인트 한 번 호출 — 즉시 backfill 트리거.
390
+ *
391
+ * - 첫 페이지뷰가 아직 백엔드에 닿기 전이면 visitor 가 없어 404. 무시 — 다음 batch 가
392
+ * 가면 백엔드 BatchRecordVisit 가 자동 LinkMember 를 호출.
393
+ * - storage_web_id 가 init 안 됐거나 모든 종류의 네트워크 오류 — silent fail.
394
+ */
395
+ private linkMemberSilent;
314
396
  /** 현재 설정된 회원 ID 조회 (미설정 시 null) */
315
397
  getMemberId(): string | null;
316
398
  /**
@@ -363,6 +445,52 @@ declare class AnalyticsAPI {
363
445
  * 방문자 목록 조회 — JWT/cb_sk_ 인증 필요.
364
446
  */
365
447
  getVisitors(storageWebId?: string, options?: VisitorListOptions): Promise<VisitorListResponse>;
448
+ /**
449
+ * 멤버별 합산 방문자 그룹 조회 — JWT/cb_sk_ 인증 필요. (1.11+)
450
+ *
451
+ * `getVisitors` 와 달리 visitor row 들을 `app_member_id` 로 합쳐 단일 row 로 반환.
452
+ * 같은 사람이 PC + 모바일 + 태블릿으로 접속한 경우 visitor 3개 → 그룹 1개.
453
+ * 익명 visitor 는 단일 row 로 그대로 포함되어 페이지네이션이 일관됨.
454
+ *
455
+ * @example
456
+ * ```ts
457
+ * const { groups } = await cb.analytics.getVisitorGroups('019d8...', { limit: 50 })
458
+ * for (const g of groups) {
459
+ * if (g.app_member_id) console.log(`${g.app_member_id}: ${g.visitor_count} 디바이스`)
460
+ * }
461
+ * ```
462
+ */
463
+ getVisitorGroups(storageWebId?: string, options?: VisitorListOptions): Promise<VisitorGroupListResponse>;
464
+ /**
465
+ * 단건 멤버 합산 방문자 조회 — JWT/cb_sk_ 인증 필요. (1.11+)
466
+ *
467
+ * 어드민 회원 상세 페이지처럼 **한 명만 필요**할 때. 페이지네이션 풀 다운 없이
468
+ * 한 번의 호출로 합산 결과 반환.
469
+ *
470
+ * @example
471
+ * ```ts
472
+ * const v = await cb.analytics.getVisitorByMember('019d8...', memberId)
473
+ * console.log(`${v.visitor_count} 디바이스, 총 ${v.total_page_views} pv`)
474
+ * ```
475
+ */
476
+ getVisitorByMember(storageWebId: string | undefined, memberId: string): Promise<VisitorByMemberResponse>;
477
+ /**
478
+ * 두 visitor 를 한 사람으로 통합하는 admin 작업 — JWT/cb_sk_ 인증 필요. (1.11+)
479
+ *
480
+ * 외부 인증 시스템에서 두 visitor 가 동일인임을 알았을 때 사용. source 의 자식 레코드
481
+ * (page_views, daily, custom_events, experiment_assignments, heatmap_events,
482
+ * session_recordings) 를 target 으로 옮기고 source visitor 는 삭제됨.
483
+ *
484
+ * @param request 둘 중 하나 필수: `target_visitor_uid` 또는 `target_member_id`.
485
+ * @example
486
+ * ```ts
487
+ * await cb.analytics.mergeVisitors('019d8...', {
488
+ * source_visitor_uid: 'old-uid',
489
+ * target_member_id: '01a...',
490
+ * })
491
+ * ```
492
+ */
493
+ mergeVisitors(storageWebId: string | undefined, request: MergeVisitorsRequest): Promise<MergeVisitorsResponse>;
366
494
  /**
367
495
  * 조회 메서드 공통 가드 — Public Key 인증 SDK 인스턴스에서는 명확한 에러를 던진다.
368
496
  * 백엔드 라우트는 cb_pk_ 를 거부하므로 호출 자체를 막는 것이 디버깅에 유리.
package/dist/index.d.ts CHANGED
@@ -241,7 +241,66 @@ interface AnalyticsRangeOptions {
241
241
  interface VisitorListOptions {
242
242
  limit?: number;
243
243
  offset?: number;
244
- sort_by?: 'last_visit' | 'total_visits' | 'total_page_views';
244
+ /**
245
+ * 백엔드가 인식하는 정렬 키 — 외 값은 silent 로 default(`last_visit`) 처리됩니다.
246
+ *
247
+ * 1.12.0 에서 백엔드 실제 동작에 맞춰 enum 정정 (1.10/1.11 의 `total_visits`/
248
+ * `total_page_views` 는 백엔드에서 인식되지 않아 항상 default 분기였음).
249
+ */
250
+ sort_by?: 'last_visit' | 'visits' | 'page_views' | 'first_visit';
251
+ }
252
+ /**
253
+ * 멤버별 합산 방문자 그룹 항목.
254
+ *
255
+ * 한 명의 회원이 여러 디바이스/브라우저로 접속했을 때 visitor row 들을 합쳐 단일 row.
256
+ * 익명 visitor 는 `app_member_id == undefined` 로 단일 row 그대로 노출되며 `visitor_count == 1`.
257
+ *
258
+ * `visitor_count` 는 "디바이스 수" 가 아닌 **"추적 브라우저 인스턴스 수"** 를 의미합니다.
259
+ * 같은 디바이스에서 시크릿모드 + 일반모드는 visitor 2 = visitor_count 2 로 카운트됩니다.
260
+ */
261
+ interface VisitorGroupItem {
262
+ app_member_id?: string;
263
+ /** 익명 visitor row 의 경우 단일 visitor_uid. 회원 그룹은 visitor_uids 참조. */
264
+ visitor_uid?: string;
265
+ /** 회원 그룹에 속한 visitor_uid 목록. 익명 row 는 undefined. */
266
+ visitor_uids?: string[];
267
+ visitor_count: number;
268
+ total_visits: number;
269
+ total_page_views: number;
270
+ first_visit_at: string;
271
+ last_visit_at: string;
272
+ country?: string;
273
+ is_bot: boolean;
274
+ }
275
+ interface VisitorGroupListResponse {
276
+ groups: VisitorGroupItem[];
277
+ total: number;
278
+ limit: number;
279
+ offset: number;
280
+ has_more: boolean;
281
+ }
282
+ interface VisitorByMemberResponse {
283
+ app_member_id: string;
284
+ visitor_uids: string[];
285
+ visitor_count: number;
286
+ total_visits: number;
287
+ total_page_views: number;
288
+ first_visit_at: string;
289
+ last_visit_at: string;
290
+ country?: string;
291
+ is_bot: boolean;
292
+ }
293
+ interface MergeVisitorsRequest {
294
+ source_visitor_uid: string;
295
+ /** target_visitor_uid 또는 target_member_id 중 하나는 필수. */
296
+ target_visitor_uid?: string;
297
+ target_member_id?: string;
298
+ }
299
+ interface MergeVisitorsResponse {
300
+ success: boolean;
301
+ target_visitor_id: string;
302
+ moved_records: number;
303
+ message?: string;
245
304
  }
246
305
  declare class SessionManager {
247
306
  private _sessionId;
@@ -302,15 +361,38 @@ declare class AnalyticsAPI {
302
361
  */
303
362
  trackEvent(name: string, properties?: Record<string, unknown>): void;
304
363
  /**
305
- * 사용자 식별 (로그인 )
306
- * 이후 모든 방문 배치에 `app_member_id`가 첨부되어 게스트 방문자가 회원으로 연결됩니다.
364
+ * 사용자 식별 (로그인 직후 호출).
365
+ *
366
+ * 이후 모든 방문 배치에 `app_member_id` 가 첨부되어 새 활동은 회원으로 기록됩니다.
367
+ * 추가로 **현재 visitor_uid 의 기존 익명 활동을 즉시 회원에게 backfill** 하기 위해
368
+ * 백엔드 `link-member` 엔드포인트를 한 번 호출합니다 (1.11.0+). 호출 실패는
369
+ * silent — 다음 batch 가 닿을 때 백엔드 자동 매핑이 동일하게 처리하므로 자가 복구.
370
+ *
371
+ * 산업 표준(GA4 User-ID, Mixpanel/PostHog `identify`) 과 동작 정합.
372
+ *
373
+ * @example
374
+ * ```ts
375
+ * // 로그인 성공 직후
376
+ * await cb.auth.signIn({ email, password })
377
+ * cb.analytics.identify(member.id)
378
+ * ```
307
379
  */
308
380
  identify(memberId: string): void;
309
381
  /**
310
- * 방문자 트래커에 현재 회원 ID 설정 (로그인/게스트 가입 시 호출)
311
- * null 을 넘기면 익명 상태로 복귀 (로그아웃).
382
+ * 방문자 트래커에 현재 회원 ID 설정 (로그인/게스트 가입 시 호출).
383
+ *
384
+ * `identify()` 와 달리 즉시 backfill 호출은 하지 않습니다 — 단순히 이후 batch 의
385
+ * `app_member_id` 값만 갱신. null 을 넘기면 익명 상태로 복귀 (로그아웃).
312
386
  */
313
387
  setMemberId(memberId: string | null): void;
388
+ /**
389
+ * 백엔드 link-member 엔드포인트 한 번 호출 — 즉시 backfill 트리거.
390
+ *
391
+ * - 첫 페이지뷰가 아직 백엔드에 닿기 전이면 visitor 가 없어 404. 무시 — 다음 batch 가
392
+ * 가면 백엔드 BatchRecordVisit 가 자동 LinkMember 를 호출.
393
+ * - storage_web_id 가 init 안 됐거나 모든 종류의 네트워크 오류 — silent fail.
394
+ */
395
+ private linkMemberSilent;
314
396
  /** 현재 설정된 회원 ID 조회 (미설정 시 null) */
315
397
  getMemberId(): string | null;
316
398
  /**
@@ -363,6 +445,52 @@ declare class AnalyticsAPI {
363
445
  * 방문자 목록 조회 — JWT/cb_sk_ 인증 필요.
364
446
  */
365
447
  getVisitors(storageWebId?: string, options?: VisitorListOptions): Promise<VisitorListResponse>;
448
+ /**
449
+ * 멤버별 합산 방문자 그룹 조회 — JWT/cb_sk_ 인증 필요. (1.11+)
450
+ *
451
+ * `getVisitors` 와 달리 visitor row 들을 `app_member_id` 로 합쳐 단일 row 로 반환.
452
+ * 같은 사람이 PC + 모바일 + 태블릿으로 접속한 경우 visitor 3개 → 그룹 1개.
453
+ * 익명 visitor 는 단일 row 로 그대로 포함되어 페이지네이션이 일관됨.
454
+ *
455
+ * @example
456
+ * ```ts
457
+ * const { groups } = await cb.analytics.getVisitorGroups('019d8...', { limit: 50 })
458
+ * for (const g of groups) {
459
+ * if (g.app_member_id) console.log(`${g.app_member_id}: ${g.visitor_count} 디바이스`)
460
+ * }
461
+ * ```
462
+ */
463
+ getVisitorGroups(storageWebId?: string, options?: VisitorListOptions): Promise<VisitorGroupListResponse>;
464
+ /**
465
+ * 단건 멤버 합산 방문자 조회 — JWT/cb_sk_ 인증 필요. (1.11+)
466
+ *
467
+ * 어드민 회원 상세 페이지처럼 **한 명만 필요**할 때. 페이지네이션 풀 다운 없이
468
+ * 한 번의 호출로 합산 결과 반환.
469
+ *
470
+ * @example
471
+ * ```ts
472
+ * const v = await cb.analytics.getVisitorByMember('019d8...', memberId)
473
+ * console.log(`${v.visitor_count} 디바이스, 총 ${v.total_page_views} pv`)
474
+ * ```
475
+ */
476
+ getVisitorByMember(storageWebId: string | undefined, memberId: string): Promise<VisitorByMemberResponse>;
477
+ /**
478
+ * 두 visitor 를 한 사람으로 통합하는 admin 작업 — JWT/cb_sk_ 인증 필요. (1.11+)
479
+ *
480
+ * 외부 인증 시스템에서 두 visitor 가 동일인임을 알았을 때 사용. source 의 자식 레코드
481
+ * (page_views, daily, custom_events, experiment_assignments, heatmap_events,
482
+ * session_recordings) 를 target 으로 옮기고 source visitor 는 삭제됨.
483
+ *
484
+ * @param request 둘 중 하나 필수: `target_visitor_uid` 또는 `target_member_id`.
485
+ * @example
486
+ * ```ts
487
+ * await cb.analytics.mergeVisitors('019d8...', {
488
+ * source_visitor_uid: 'old-uid',
489
+ * target_member_id: '01a...',
490
+ * })
491
+ * ```
492
+ */
493
+ mergeVisitors(storageWebId: string | undefined, request: MergeVisitorsRequest): Promise<MergeVisitorsResponse>;
366
494
  /**
367
495
  * 조회 메서드 공통 가드 — Public Key 인증 SDK 인스턴스에서는 명확한 에러를 던진다.
368
496
  * 백엔드 라우트는 cb_pk_ 를 거부하므로 호출 자체를 막는 것이 디버깅에 유리.
package/dist/index.js CHANGED
@@ -8384,15 +8384,31 @@ var AnalyticsAPI = class {
8384
8384
  this.enqueue(event);
8385
8385
  }
8386
8386
  /**
8387
- * 사용자 식별 (로그인 )
8388
- * 이후 모든 방문 배치에 `app_member_id`가 첨부되어 게스트 방문자가 회원으로 연결됩니다.
8387
+ * 사용자 식별 (로그인 직후 호출).
8388
+ *
8389
+ * 이후 모든 방문 배치에 `app_member_id` 가 첨부되어 새 활동은 회원으로 기록됩니다.
8390
+ * 추가로 **현재 visitor_uid 의 기존 익명 활동을 즉시 회원에게 backfill** 하기 위해
8391
+ * 백엔드 `link-member` 엔드포인트를 한 번 호출합니다 (1.11.0+). 호출 실패는
8392
+ * silent — 다음 batch 가 닿을 때 백엔드 자동 매핑이 동일하게 처리하므로 자가 복구.
8393
+ *
8394
+ * 산업 표준(GA4 User-ID, Mixpanel/PostHog `identify`) 과 동작 정합.
8395
+ *
8396
+ * @example
8397
+ * ```ts
8398
+ * // 로그인 성공 직후
8399
+ * await cb.auth.signIn({ email, password })
8400
+ * cb.analytics.identify(member.id)
8401
+ * ```
8389
8402
  */
8390
8403
  identify(memberId) {
8391
8404
  this.setMemberId(memberId);
8405
+ this.linkMemberSilent(memberId);
8392
8406
  }
8393
8407
  /**
8394
- * 방문자 트래커에 현재 회원 ID 설정 (로그인/게스트 가입 시 호출)
8395
- * null 을 넘기면 익명 상태로 복귀 (로그아웃).
8408
+ * 방문자 트래커에 현재 회원 ID 설정 (로그인/게스트 가입 시 호출).
8409
+ *
8410
+ * `identify()` 와 달리 즉시 backfill 호출은 하지 않습니다 — 단순히 이후 batch 의
8411
+ * `app_member_id` 값만 갱신. null 을 넘기면 익명 상태로 복귀 (로그아웃).
8396
8412
  */
8397
8413
  setMemberId(memberId) {
8398
8414
  this.memberId = memberId || null;
@@ -8409,6 +8425,25 @@ var AnalyticsAPI = class {
8409
8425
  }
8410
8426
  this.log("Member id set", { memberId });
8411
8427
  }
8428
+ /**
8429
+ * 백엔드 link-member 엔드포인트 한 번 호출 — 즉시 backfill 트리거.
8430
+ *
8431
+ * - 첫 페이지뷰가 아직 백엔드에 닿기 전이면 visitor 가 없어 404. 무시 — 다음 batch 가
8432
+ * 가면 백엔드 BatchRecordVisit 가 자동 LinkMember 를 호출.
8433
+ * - storage_web_id 가 init 안 됐거나 모든 종류의 네트워크 오류 — silent fail.
8434
+ */
8435
+ linkMemberSilent(memberId) {
8436
+ if (!this.storageWebId) return;
8437
+ const prefix = this.http.hasPublicKey() ? "/v1/public" : "/v1";
8438
+ this.http.post(
8439
+ `${prefix}/storages/web/${this.storageWebId}/visitors/link-member`,
8440
+ {
8441
+ visitor_uid: this.session.visitorUid,
8442
+ app_member_id: memberId
8443
+ }
8444
+ ).catch(() => {
8445
+ });
8446
+ }
8412
8447
  /** 현재 설정된 회원 ID 조회 (미설정 시 null) */
8413
8448
  getMemberId() {
8414
8449
  return this.memberId;
@@ -8540,6 +8575,66 @@ var AnalyticsAPI = class {
8540
8575
  const qs = params.toString() ? `?${params.toString()}` : "";
8541
8576
  return this.http.get(`/v1/storages/web/${id}/visitors${qs}`);
8542
8577
  }
8578
+ /**
8579
+ * 멤버별 합산 방문자 그룹 조회 — JWT/cb_sk_ 인증 필요. (1.11+)
8580
+ *
8581
+ * `getVisitors` 와 달리 visitor row 들을 `app_member_id` 로 합쳐 단일 row 로 반환.
8582
+ * 같은 사람이 PC + 모바일 + 태블릿으로 접속한 경우 visitor 3개 → 그룹 1개.
8583
+ * 익명 visitor 는 단일 row 로 그대로 포함되어 페이지네이션이 일관됨.
8584
+ *
8585
+ * @example
8586
+ * ```ts
8587
+ * const { groups } = await cb.analytics.getVisitorGroups('019d8...', { limit: 50 })
8588
+ * for (const g of groups) {
8589
+ * if (g.app_member_id) console.log(`${g.app_member_id}: ${g.visitor_count} 디바이스`)
8590
+ * }
8591
+ * ```
8592
+ */
8593
+ async getVisitorGroups(storageWebId, options) {
8594
+ const id = this.requireServerSideStorageId(storageWebId, "getVisitorGroups");
8595
+ const params = new URLSearchParams();
8596
+ if (options?.limit !== void 0) params.set("limit", String(options.limit));
8597
+ if (options?.offset !== void 0) params.set("offset", String(options.offset));
8598
+ if (options?.sort_by) params.set("sort_by", options.sort_by);
8599
+ const qs = params.toString() ? `?${params.toString()}` : "";
8600
+ return this.http.get(`/v1/storages/web/${id}/visitor-groups${qs}`);
8601
+ }
8602
+ /**
8603
+ * 단건 멤버 합산 방문자 조회 — JWT/cb_sk_ 인증 필요. (1.11+)
8604
+ *
8605
+ * 어드민 회원 상세 페이지처럼 **한 명만 필요**할 때. 페이지네이션 풀 다운 없이
8606
+ * 한 번의 호출로 합산 결과 반환.
8607
+ *
8608
+ * @example
8609
+ * ```ts
8610
+ * const v = await cb.analytics.getVisitorByMember('019d8...', memberId)
8611
+ * console.log(`${v.visitor_count} 디바이스, 총 ${v.total_page_views} pv`)
8612
+ * ```
8613
+ */
8614
+ async getVisitorByMember(storageWebId, memberId) {
8615
+ const id = this.requireServerSideStorageId(storageWebId, "getVisitorByMember");
8616
+ return this.http.get(`/v1/storages/web/${id}/members/${memberId}/visitor`);
8617
+ }
8618
+ /**
8619
+ * 두 visitor 를 한 사람으로 통합하는 admin 작업 — JWT/cb_sk_ 인증 필요. (1.11+)
8620
+ *
8621
+ * 외부 인증 시스템에서 두 visitor 가 동일인임을 알았을 때 사용. source 의 자식 레코드
8622
+ * (page_views, daily, custom_events, experiment_assignments, heatmap_events,
8623
+ * session_recordings) 를 target 으로 옮기고 source visitor 는 삭제됨.
8624
+ *
8625
+ * @param request 둘 중 하나 필수: `target_visitor_uid` 또는 `target_member_id`.
8626
+ * @example
8627
+ * ```ts
8628
+ * await cb.analytics.mergeVisitors('019d8...', {
8629
+ * source_visitor_uid: 'old-uid',
8630
+ * target_member_id: '01a...',
8631
+ * })
8632
+ * ```
8633
+ */
8634
+ async mergeVisitors(storageWebId, request) {
8635
+ const id = this.requireServerSideStorageId(storageWebId, "mergeVisitors");
8636
+ return this.http.post(`/v1/storages/web/${id}/visitors/merge`, request);
8637
+ }
8543
8638
  /**
8544
8639
  * 조회 메서드 공통 가드 — Public Key 인증 SDK 인스턴스에서는 명확한 에러를 던진다.
8545
8640
  * 백엔드 라우트는 cb_pk_ 를 거부하므로 호출 자체를 막는 것이 디버깅에 유리.
package/dist/index.mjs CHANGED
@@ -8346,15 +8346,31 @@ var AnalyticsAPI = class {
8346
8346
  this.enqueue(event);
8347
8347
  }
8348
8348
  /**
8349
- * 사용자 식별 (로그인 )
8350
- * 이후 모든 방문 배치에 `app_member_id`가 첨부되어 게스트 방문자가 회원으로 연결됩니다.
8349
+ * 사용자 식별 (로그인 직후 호출).
8350
+ *
8351
+ * 이후 모든 방문 배치에 `app_member_id` 가 첨부되어 새 활동은 회원으로 기록됩니다.
8352
+ * 추가로 **현재 visitor_uid 의 기존 익명 활동을 즉시 회원에게 backfill** 하기 위해
8353
+ * 백엔드 `link-member` 엔드포인트를 한 번 호출합니다 (1.11.0+). 호출 실패는
8354
+ * silent — 다음 batch 가 닿을 때 백엔드 자동 매핑이 동일하게 처리하므로 자가 복구.
8355
+ *
8356
+ * 산업 표준(GA4 User-ID, Mixpanel/PostHog `identify`) 과 동작 정합.
8357
+ *
8358
+ * @example
8359
+ * ```ts
8360
+ * // 로그인 성공 직후
8361
+ * await cb.auth.signIn({ email, password })
8362
+ * cb.analytics.identify(member.id)
8363
+ * ```
8351
8364
  */
8352
8365
  identify(memberId) {
8353
8366
  this.setMemberId(memberId);
8367
+ this.linkMemberSilent(memberId);
8354
8368
  }
8355
8369
  /**
8356
- * 방문자 트래커에 현재 회원 ID 설정 (로그인/게스트 가입 시 호출)
8357
- * null 을 넘기면 익명 상태로 복귀 (로그아웃).
8370
+ * 방문자 트래커에 현재 회원 ID 설정 (로그인/게스트 가입 시 호출).
8371
+ *
8372
+ * `identify()` 와 달리 즉시 backfill 호출은 하지 않습니다 — 단순히 이후 batch 의
8373
+ * `app_member_id` 값만 갱신. null 을 넘기면 익명 상태로 복귀 (로그아웃).
8358
8374
  */
8359
8375
  setMemberId(memberId) {
8360
8376
  this.memberId = memberId || null;
@@ -8371,6 +8387,25 @@ var AnalyticsAPI = class {
8371
8387
  }
8372
8388
  this.log("Member id set", { memberId });
8373
8389
  }
8390
+ /**
8391
+ * 백엔드 link-member 엔드포인트 한 번 호출 — 즉시 backfill 트리거.
8392
+ *
8393
+ * - 첫 페이지뷰가 아직 백엔드에 닿기 전이면 visitor 가 없어 404. 무시 — 다음 batch 가
8394
+ * 가면 백엔드 BatchRecordVisit 가 자동 LinkMember 를 호출.
8395
+ * - storage_web_id 가 init 안 됐거나 모든 종류의 네트워크 오류 — silent fail.
8396
+ */
8397
+ linkMemberSilent(memberId) {
8398
+ if (!this.storageWebId) return;
8399
+ const prefix = this.http.hasPublicKey() ? "/v1/public" : "/v1";
8400
+ this.http.post(
8401
+ `${prefix}/storages/web/${this.storageWebId}/visitors/link-member`,
8402
+ {
8403
+ visitor_uid: this.session.visitorUid,
8404
+ app_member_id: memberId
8405
+ }
8406
+ ).catch(() => {
8407
+ });
8408
+ }
8374
8409
  /** 현재 설정된 회원 ID 조회 (미설정 시 null) */
8375
8410
  getMemberId() {
8376
8411
  return this.memberId;
@@ -8502,6 +8537,66 @@ var AnalyticsAPI = class {
8502
8537
  const qs = params.toString() ? `?${params.toString()}` : "";
8503
8538
  return this.http.get(`/v1/storages/web/${id}/visitors${qs}`);
8504
8539
  }
8540
+ /**
8541
+ * 멤버별 합산 방문자 그룹 조회 — JWT/cb_sk_ 인증 필요. (1.11+)
8542
+ *
8543
+ * `getVisitors` 와 달리 visitor row 들을 `app_member_id` 로 합쳐 단일 row 로 반환.
8544
+ * 같은 사람이 PC + 모바일 + 태블릿으로 접속한 경우 visitor 3개 → 그룹 1개.
8545
+ * 익명 visitor 는 단일 row 로 그대로 포함되어 페이지네이션이 일관됨.
8546
+ *
8547
+ * @example
8548
+ * ```ts
8549
+ * const { groups } = await cb.analytics.getVisitorGroups('019d8...', { limit: 50 })
8550
+ * for (const g of groups) {
8551
+ * if (g.app_member_id) console.log(`${g.app_member_id}: ${g.visitor_count} 디바이스`)
8552
+ * }
8553
+ * ```
8554
+ */
8555
+ async getVisitorGroups(storageWebId, options) {
8556
+ const id = this.requireServerSideStorageId(storageWebId, "getVisitorGroups");
8557
+ const params = new URLSearchParams();
8558
+ if (options?.limit !== void 0) params.set("limit", String(options.limit));
8559
+ if (options?.offset !== void 0) params.set("offset", String(options.offset));
8560
+ if (options?.sort_by) params.set("sort_by", options.sort_by);
8561
+ const qs = params.toString() ? `?${params.toString()}` : "";
8562
+ return this.http.get(`/v1/storages/web/${id}/visitor-groups${qs}`);
8563
+ }
8564
+ /**
8565
+ * 단건 멤버 합산 방문자 조회 — JWT/cb_sk_ 인증 필요. (1.11+)
8566
+ *
8567
+ * 어드민 회원 상세 페이지처럼 **한 명만 필요**할 때. 페이지네이션 풀 다운 없이
8568
+ * 한 번의 호출로 합산 결과 반환.
8569
+ *
8570
+ * @example
8571
+ * ```ts
8572
+ * const v = await cb.analytics.getVisitorByMember('019d8...', memberId)
8573
+ * console.log(`${v.visitor_count} 디바이스, 총 ${v.total_page_views} pv`)
8574
+ * ```
8575
+ */
8576
+ async getVisitorByMember(storageWebId, memberId) {
8577
+ const id = this.requireServerSideStorageId(storageWebId, "getVisitorByMember");
8578
+ return this.http.get(`/v1/storages/web/${id}/members/${memberId}/visitor`);
8579
+ }
8580
+ /**
8581
+ * 두 visitor 를 한 사람으로 통합하는 admin 작업 — JWT/cb_sk_ 인증 필요. (1.11+)
8582
+ *
8583
+ * 외부 인증 시스템에서 두 visitor 가 동일인임을 알았을 때 사용. source 의 자식 레코드
8584
+ * (page_views, daily, custom_events, experiment_assignments, heatmap_events,
8585
+ * session_recordings) 를 target 으로 옮기고 source visitor 는 삭제됨.
8586
+ *
8587
+ * @param request 둘 중 하나 필수: `target_visitor_uid` 또는 `target_member_id`.
8588
+ * @example
8589
+ * ```ts
8590
+ * await cb.analytics.mergeVisitors('019d8...', {
8591
+ * source_visitor_uid: 'old-uid',
8592
+ * target_member_id: '01a...',
8593
+ * })
8594
+ * ```
8595
+ */
8596
+ async mergeVisitors(storageWebId, request) {
8597
+ const id = this.requireServerSideStorageId(storageWebId, "mergeVisitors");
8598
+ return this.http.post(`/v1/storages/web/${id}/visitors/merge`, request);
8599
+ }
8505
8600
  /**
8506
8601
  * 조회 메서드 공통 가드 — Public Key 인증 SDK 인스턴스에서는 명확한 에러를 던진다.
8507
8602
  * 백엔드 라우트는 cb_pk_ 를 거부하므로 호출 자체를 막는 것이 디버깅에 유리.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "connectbase-client",
3
- "version": "1.10.0",
3
+ "version": "1.12.0",
4
4
  "description": "Connect Base JavaScript/TypeScript SDK for browser and Node.js",
5
5
  "repository": {
6
6
  "type": "git",