@abto-app/event
v1.1.0
Published
ABTO Browser SDK — typed custom events, identity, sessions, AI trace correlation, and opt-in autocapture.
Readme
@abto-app/event
ABTO SDK는 고객사가 선택해 직접 기록한 사용자 행동을 수집하고, Gateway가 반환한 request_id를 통해 서버의 LLM 비용·지연 데이터와 연결한다.
@abto-app/event는 공개키를 사용하는 Browser 전용 패키지다.
비밀키와 provider credential을 사용하는 Node 환경에는 별도 @abto-app/calling 패키지를 설치한다.
PostHog의 autocapture, ingestion, session, schema/discovery는 ABTO 이벤트 설계의 참고 모델이다. ABTO 이벤트를 PostHog로 보내는 연동이 아니라, 검증된 수집 철학을 ABTO 독립 수집 구조에 적용한다.
설치
pnpm add @abto-app/event이벤트 경계
Browser SDK가 보내는 이벤트는 두 종류다.
| 종류 | SDK 이름 | Analytics wire 이름 | 정의 주체 | 발생 방식 |
|---|---|---|---|---|
| 시스템 이벤트 | $로 시작 | ABTO canonical 이름 | ABTO | 명시적으로 켠 autocapture 또는 전용 API |
| 커스텀 이벤트 | $ 없이 제품 도메인 이름 사용 | 같은 이름 | 고객 저장소 | client.capture() |
사용자는 $ 이벤트나 $ 속성을 등록할 수 없다. 아래 canonical wire 이름도 ABTO가 소유하므로 커스텀 이벤트로 등록할 수 없다. ABTO 시스템 이벤트는 일반 capture()로 보낼 수 없으며 SDK 내부 경로와 AI trace 전용 메서드만 발생시킨다.
Browser SDK 시스템 이벤트
| SDK 이벤트 | Analytics event_name | 의미 | 발생 조건 |
|---|---|---|---|
| $pageview | pageview | 페이지/SPA route 진입 | autocapture opt-in 시 초기 로드, history 변경, bfcache 복원 |
| $pageleave | pageleave | 페이지/route 이탈 | autocapture opt-in 시 SPA 이동, pagehide |
| $autocapture | interaction_autocaptured | DOM 상호작용 원시 사실 | autocapture opt-in 시 click, change, submit, copy |
| $rageclick | interaction_rageclick | 짧은 시간의 반복 클릭 | autocapture opt-in 시 SDK 휴리스틱 |
| $dead_click | interaction_deadclick | 반응이 관측되지 않은 클릭 | autocapture opt-in 시 SDK 휴리스틱 |
| $ai_prompt_submitted | llm_prompt_submitted | 프롬프트 제출을 앱이 확인 | trace.submitPrompt() |
| $ai_response_rendered | llm_response_rendered | 응답이 UI에 렌더됨을 앱이 확인 | trace.markResponseRendered() |
| $ai_response_interacted | llm_response_interacted | 응답에 대한 명시적 행동 | trace.captureResponseInteraction() |
$session_start와 $session_end는 보내지 않는다. 모든 이벤트의 session_id와 timestamp의 최솟값·최댓값을 분석 계층에서 사용해 세션 시작, 종료, duration을 파생한다. 브라우저 종료 신호는 유실될 수 있으므로 $session_end를 확정 사실로 기록하지 않는다.
커스텀 이벤트 정본: abto.events.ts
제품 이벤트는 고객 애플리케이션 저장소의 abto.events.ts에 사전 등록한다. 이 파일을 코드 리뷰와 향후 CLI/CI schema push의 정본으로 사용한다.
// abto.events.ts
import { defineEvents } from '@abto-app/event';
export const events = defineEvents({
checkout_completed: { description: '결제가 완료됨' },
});import { initAbto } from '@abto-app/event';
import { events } from './abto.events';
const abto = initAbto({
projectKey: 'public_project_key',
environment: 'development',
events,
});
abto.capture('checkout_completed', {
value: 49000,
scale: 'KRW',
});value와 scale은 모두 선택이다. 이름만 보내거나 추가 속성만 보낼 수 있다. 생략한 value와 scale은 전송하지 않고, 빈 문자열 scale은 그대로 전송한다.
단순 행동 건수는 { value: 1, scale: 'count' }로 기록한다.
Success Metric은 이벤트 이름과 metric을 집계하며, 같은 객체에 추가한 나머지 속성은 자동으로 extra_json에 들어간다.
defineEvents()에서 이름을 추론하므로 잘못된 이벤트 이름을 개발 시점에 확인할 수 있다. 런타임 정책은 환경별로 다르다.
| 환경 | 미등록 이벤트 |
|---|---|
| development | 전송하고 Discovered 경고 |
| production | drop |
필수 metric이 없거나 계약을 벗어나면 경고 후 해당 커스텀 이벤트를 보내지 않는다.
추가 속성은 같은 객체에 넣는다. value·scale은 최상위 metric으로, tier는 extra_json.tier로 전송된다.
abto.capture('checkout_completed', { value: 49000, scale: 'KRW', tier: 'pro' });초기화와 autocapture
앱 루트에서 한 번 초기화한다. 설정을 생략한 기본 초기화는 identity와 trace context만 준비하며 이벤트를 만들지 않는다. 고객사가 필요하다고 정한 Custom Event와 LLM trace event만 해당 제품 동작에서 직접 호출한다.
const abto = initAbto({
projectKey: 'public_project_key',
apiHost: 'https://api.abto.app',
environment: 'production',
events,
});| 설정 | 기본값 | 역할 |
|---|---|---|
| projectKey | 필수 | 브라우저에 둘 수 있는 공개 Event Key |
| apiHost | https://api.abto.app | Event API host. SDK가 /v1/collect/events를 붙인다 |
| environment | production | development에서는 미등록 event와 schema drift를 경고 후 전송한다 |
| appVersion | 미설정 | 지정하면 event context의 $app_version으로 전송한다 |
| events | {} | defineEvents()로 만든 Custom Event registry |
| capture.prompt | metadata_only | off, hash, metadata_only, full 중 prompt 수집 정책 |
| capture.response | metadata_only | off, metadata_only, full 중 response 수집 정책 |
| capture.mask | all | off, sensitive, all 중 autocapture DOM text/value 마스킹 정책 |
| autocapture.enabled | false | 페이지와 DOM 자동 수집의 명시적 opt-in |
페이지와 DOM 상호작용을 넓게 수집해야 하고 데이터 정책 검토를 마친 경우에만 autocapture를 명시적으로 켠다.
const abto = initAbto({
projectKey: 'public_project_key',
autocapture: { enabled: true },
});이전 기본값에서 마이그레이션
0.1.4 이하에서 autocapture를 생략하면 자동 수집이 켜졌다. 그 동작에 의존한 애플리케이션은 업그레이드할 때 autocapture: { enabled: true }를 추가해야 한다. 자동 수집이 필요하지 않은 애플리케이션은 설정을 생략하고 선택한 capture()와 LlmTrace 호출만 유지한다.
기본 endpoint는 ${apiHost}/v1/collect/events다. SDK 내부의 Browser 이벤트는 전송 직전에
현재 Analytics 수신 계약으로 변환된다.
{
"batch": [
{
"event_id": "019b...",
"device_id": "019b...",
"session_id": "019b...",
"event_name": "checkout_started",
"value": 3000,
"scale": "KRW",
"occurred_at": "2026-07-15T04:10:00.000Z",
"extra_json": {
"value": 3000,
"scale": "KRW",
"$lib": "web",
"$lib_version": "1.1.0"
}
}
]
}서버의 이벤트별 응답은 event UUID를 key로 사용한다.
{
"results": {
"019b5b74-11d0-7000-8000-000000000001": {
"result": "drop",
"code": "schema_type_mismatch"
}
}
}모든 collector 요청은 public project key를 Bearer header에 싣는다. 페이지 이탈도 응답을 읽을 수 있는 fetch(..., { keepalive: true })를 사용하며, 서버가 이 key에서 project_id와 organization_id를 결정한다. $tenant_id를 포함한 client property는 분석 문맥이며 인증·project 귀속 값이 아니다.
수신 계약의 상한은 요청당 100 events다. SDK 기본값은 20이며 약 60 KiB 이하 payload만 keepalive로 전송한다. malformed request와 인증 실패는 요청 단위 4xx, 개별 validation/storage 실패는 2xx 응답의 UUID별 warning, drop, retry로 처리한다.
커스텀 event_name은 Backend와 같은 UTF-16 기준 최대 200자이며, defineEvents()와 runtime capture가 enqueue 전에 검증한다.
metric scale은 최대 16자이며, 빈 값이나 초과한 값은 해당 커스텀 이벤트를 경고 후 보내지 않는다.
SDK 자체 전송 진단
Browser SDK는 send_failed, outbox_write_failed, identity_persist_failed, storage_unavailable 네 실패를
메모리 counter로만 모은다. 다음 event batch가 있을 때 고정 스키마의 선택적 diagnostics 필드로 같은 요청에
동승시키며, 이를 위한 별도 네트워크 요청이나 제품 event를 만들지 않는다. 성공 응답을 받은 counter만 차감하고
전송 실패 시 보존한다.
{
"batch": [{ "event_id": "019b...", "event_name": "checkout_started" }],
"diagnostics": {
"sdk_name": "browser-javascript",
"counters": { "send_failed": 1 }
}
}diagnostics는 위 고정 SDK 이름과 counter만 포함하고 사용자 ID, event property, URL, 오류 원문을 복사하지 않는다. 고정된 네 counter만 직렬화하므로 별도 크기 제한이나 첨부 복구 경로가 필요하지 않다. event가 전혀 없으면 진단만 보내는 요청도 생기지 않는다.
SDK 내부 queue와 API에서는 $ 이름을 유지하지만, Analytics의 고정 Event 계약은 $ 접두
event_name을 거절한다. Transport가 위 표의 canonical 이름으로만 변환해 전송한다.
device_id·session_id·trace_id는 wire의 1급 필드로 실리고, 담을 필드가 없는
$lib·$user_id·$feature_id 같은 SDK 소유 context만 extra_json에 남는다.
Dashboard 이벤트 카탈로그와 Success Metric에서는 canonical wire 이름을 사용한다.
autocapture를 명시적으로 켠 경우 annotation은 원시 $autocapture를 다른 이벤트로 바꾸지 않는다. 원시 상호작용을 보존하면서 분석 차원만 보강한다.
<button
data-abto-action="accept"
data-abto-surface="generator"
data-abto-feature-id="resume.make"
data-abto-response-id="resp_123"
data-abto-request-id="req_123">
적용
</button>autocapture를 켰다면 위 클릭은 $autocapture로 수집되며 $ai_action, $surface, $feature_id, $response_id, $request_id가 함께 실린다. 업무 의미가 확정된 행동은 앱 코드에서 커스텀 이벤트 또는 AI 전용 메서드로 별도 기록한다.
개인정보 기본값
prompt, response, DOM text/value는 기본적으로 원문을 수집하지 않는다.
위 설정 표의 capture.prompt, capture.response, capture.mask를 생략해도
각각 metadata_only, metadata_only, all의 안전한 기본값이 적용된다.
| annotation | 동작 |
|---|---|
| data-abto-no-capture | 자신과 하위 트리를 수집하지 않음 |
| data-abto-sensitive | 자신과 하위 text/value를 항상 전체 마스킹 |
| data-abto-include | 해당 요소의 text/value 수집을 명시적으로 허용 |
password, hidden input과 카드·비밀번호·SSN 계열 필드는 annotation과 무관하게 보호한다. full 원문 수집은 명시적 opt-in이며 고객의 동의·보존·삭제 정책과 함께 사용해야 한다.
브라우저에서 관측 가능한 AI 이벤트
브라우저가 확실히 아는 세 가지 사실만 전용 API로 제공한다.
const trace = abto.startLlmTrace();
await trace.submitPrompt({
prompt: promptText,
language: 'ko',
});
const response = await fetch('/api/generate', {
method: 'POST',
headers: { 'content-type': 'application/json', ...trace.getHeaders() },
body: JSON.stringify({ prompt: promptText }),
});
trace.attachRequestId(response);
await trace.markResponseRendered({
responseId: 'resp_123',
timeToRenderMs: 1380,
});
await trace.captureResponseInteraction('copied', {
responseId: 'resp_123',
source: 'copy_button',
});captureResponseInteraction()은 계약에 정의된 12개 interaction type만 enqueue한다.
plain JavaScript에서 다른 값이 들어와도 경고 후 제외하며, 제품 고유 행동은 커스텀 이벤트로 기록한다.
provider/model/token/cost/retry/fallback, 실제 첫 토큰 시점과 request 성공·실패는 Server SDK/Gateway가 소유한다. AI task 완료·이탈은 제품마다 의미가 다르므로 커스텀 이벤트 또는 분석 파생 지표로 둔다.
trace.getHeaders()는 브라우저가 소유하는 x-abto-device-id만 반환한다.
실제 모델 호출의 featureId는 브라우저 입력을 신뢰하지 않고 백엔드가 Calling SDK context에 설정한다.
식별자와 세션
$ 접두가 붙은 것은 extra_json이 유일한 자리인 context이고, 나머지는 wire의 1급 필드다.
| 속성 | 수명과 역할 |
|---|---|
| device_id | 프로젝트별 브라우저 설치, localStorage 유지 |
| session_id | 탭 사이에서 공유하는 논리 세션, 30분 idle 또는 24시간 max age에 회전 |
| trace_id | 한 사용자 행동에서 발생한 브라우저 이벤트를 묶는 값 |
| $user_id | identify()로 연결한 제품 사용자 |
| $window_id | 탭/window별 ID, sessionStorage 유지 |
| $pageview_id | 페이지/SPA route 구간, pageview마다 회전 |
| $request_id | Gateway의 실제 provider 호출 PK |
abto.identify('user_123', 'tenant_123');
abto.reset(); // user/tenant 제거, device 유지
abto.forgetDevice(); // outbox와 device identity 제거identity 저장이 실패해도 새 session/device ID는 현재 인스턴스의 메모리에 유지된다. 저장이 복구되면 현재 값을 다시 보관한다. 활동 시각만 저장하다 실패한 경우에는 다른 탭의 session/device ID 변경을 계속 반영한다.
전송과 재시도
- 이벤트는 프로젝트별 localStorage outbox에 이벤트별 항목으로 먼저 저장한다. 여러 탭의 enqueue·ack가 다른 이벤트를 덮어쓰지 않는다.
- 이벤트별 순번으로 보관 순서를 유지한다. 같은 시각의 이벤트도 UUID 정렬에 의존하지 않고 오래된 항목부터 버퍼 상한을 적용한다.
- 기존 배열 형식 outbox는 읽을 때 이벤트별 항목으로 옮긴다. 저장 실패 시 메모리 큐를 유지하고 다음 flush에서 저장을 재시도한다.
- 여러 인스턴스가 같은 이벤트를 재시도할 수 있으며 UUID 기반 서버 중복 처리를 따른다.
- 기본적으로 최대 20개씩
POST /v1/collect/events로 보낸다. - 일반 flush와 페이지 이탈 모두 응답 가능한
fetch를 사용하며, 안전 크기의 이탈 payload에만keepalive를 켠다. - keepalive payload는 약 60 KiB 이내로 제한한다. 응답 전에 페이지가 종료되면 localStorage outbox가 다음 SDK 인스턴스에서 재전송한다.
- 408, 429, 5xx와 이벤트별
retry만 지수 backoff로 재시도한다. - 영구 4xx와 이벤트별
drop은 outbox에서 제거한다. - 이벤트별
ok,warning,drop,retry응답을 UUID 기준으로 처리한다.
Public API (browser)
initAbto · defineEvents · client.identify · client.getIdentity · client.reset · client.forgetDevice · client.startLlmTrace · client.capture · client.flush · client.shutdown · trace.getHeaders · trace.submitPrompt · trace.markResponseRendered · trace.captureResponseInteraction.
개발 검증
pnpm test
pnpm typecheck
pnpm build
node ../../examples/browser-smoke/collector.mjs실브라우저 검증 절차는 examples/browser-smoke/README.md를 따른다.
추가 속성 값은 JSON 스칼라(문자열·유한한 수·불리언·null), 스칼라 배열, 스칼라 값으로 구성된 객체를 받습니다.
더 깊은 중첩, U+0000, 최상위 $ 접두 키는 해당 커스텀 이벤트와 함께 거절합니다.
이벤트 ID·시각·기기/세션 ID·$ 문맥은 SDK가 자동으로 추가합니다.
이 capture API는 1.0.0의 위치 인자 API를 대체합니다. 업그레이드 시 호출부를 바꿔야 하며, 기존 저장 이벤트의 해석은 유지됩니다.
