allob Docsallob 열기

앱에 측정 넣기

React Native·Expo 앱이나 웹뷰 앱(Capacitor 등)에 allob 앱 SDK 를 넣는 방법이에요. 기기 ID, 동의, 세션, 전송은 SDK 가 처리해요. 앱은 값을 저장할 곳, 앱이 화면에 떠 있는지 알려 주는 신호, 네트워크 전송 방법 세 가지만 SDK 에 넘겨주면 돼요. 앱 버전도 넘기면 릴리스를 버전별로 비교할 수 있어요.

에이전트에게 맡기기

코딩 에이전트에게 붙여 넣기

https://allob.app/docs/app.md 를 읽고 이 앱에 allob 앱 SDK 를 넣어 줘.
1. allob schema pull 로 스키마를 받고, 앱 이벤트에 "surface": "app" 이 있는지 확인해. 없으면 추가해서 allob schema push 해.
2. allob.json 의 "sdk" 에 app 경로가 앱 코드 폴더 안(예: "app/src/allob")을 가리키는지 확인하고, 없거나 다르면 고쳐.
3. allob sdk pull 로 app.ts 를 받아.
4. 앱이 켜질 때 buildApp() 으로 만들고 start() 와 askCountry() 를 불러. buildApp 에 앱 버전(version)을 넘겨 — Expo 면 expo-application(OTA 를 쓰면 expo-updates 도)을 설치하고 version: expoVersion(Application, Updates) 로.
5. 앱 열기·화면 이동·핵심 행동에서 이벤트를 보내.
6. 결제 직전에 buyerSid() 값을 결제 서비스에 넘겨서 서버가 결제를 이 기기에 연결할 수 있게 해. storePurchase 를 부르면 시험 결제에는 sandbox: true 를 넣어.
7. 개발 빌드는 이벤트를 보내지 않아. 개발 빌드에서 확인하려면 sendInDev: true 로 잠깐 켜고, 끝나면 그 기기를 빼(https://allob.app/docs/app.md 의 "내 기기 빼기").
끝나면 바꾼 파일과 각 이벤트를 보내는 위치를 알려 줘.

앱 SDK 받기

앱 SDK 는 allob.json 의 "sdk" 에 적은 app 폴더에 들어가요. allob folder init 은 폴더를 보고 대상을 골라요. package.json 에 Expo·React Native 가 있으면 "sdk" 에 "app": "allob" 이 들어가요. 폴더에서 알 수 있는 게 없을 때만 제품 플랫폼으로 정해요(iOS·Android·macOS 가 있으면 app). 스키마에 앱 이벤트가 아직 없으면 앱 SDK 는 이번엔 받지 않고, 이벤트를 추가한 뒤 allob sdk pull 로 받아요. 웹과 앱이 한 저장소에 있으면 폴더마다 적어요.

"sdk": { "web": "web/src/allob", "app": "app/src/allob" }
allob sdk pull
파일내용
allob/app.tsbuildApp() 함수와 수집 주소(ALLOB_APP)
allob/analytics.ts이벤트별 함수와 타입
allob/sdk/앱 SDK 본체(기기 ID, 동의, 세션, 전송)

아래 예시의 ./allob/ 은 이 폴더를 말해요. 앱은 내 서버를 거치지 않고 allob 으로 바로 보내요. 이미 내 서버로 보내던 옛 앱 버전이 있다면 이미 설치된 옛 앱 버전을 봐요.

앱 이벤트 정의하기

스키마에 앱 이벤트가 있어야 앱 SDK 를 받을 수 있어요. 앱 이벤트가 없으면 allob sdk pull 이 앱 이벤트를 먼저 추가하라고 알려 줘요. 이벤트마다 "surface": "app" 을 적거나, 제품 전체가 앱이면 tracking.surface 를 "app" 으로 둬요. 제품 플랫폼에 앱(iOS·Android·macOS)을 고르고 allob folder init 으로 시작했다면 첫 스키마에 앱 열기 이벤트 app_open 이 들어 있어요. 앱만 골랐다면 tracking.surface 도 "app" 이고, 웹과 앱을 함께 골랐다면 tracking.surface 는 "web" 이고 app_open 에만 "surface": "app" 이 붙어요.

"events": {
  "app_open": { "label": "앱 열기", "surface": "app", "props": { "how": { "enum": ["cold", "resume"] } }, "order": ["how"] },
  "screen_view": { "label": "화면 보기", "surface": "app", "props": { "screen": { "enum": ["home", "settings"] } }, "order": ["screen"] },
  "entry_write": { "label": "일기 쓰기", "surface": "app" }
}

React Native·Expo

import { AppState } from "react-native";
import AsyncStorage from "@react-native-async-storage/async-storage";
import * as Application from "expo-application";
import * as Updates from "expo-updates";
import { buildApp, ALLOB_APP, appStateLifecycle, expoVersion, fetchNetwork } from "./allob/app.ts";

const storage = {
  get: (k: string) => AsyncStorage.getItem(k),
  set: (k: string, v: string) => AsyncStorage.setItem(k, v),
  delete: (k: string) => AsyncStorage.removeItem(k),
};

export const allob = buildApp({
  network: fetchNetwork(ALLOB_APP.endpoint),
  lifecycle: appStateLifecycle(AppState),
  storage,
  prompt: true,          // 앱에 동의 창이 있으면 true
  version: expoVersion(Application, Updates),   // 앱 버전·빌드 번호·OTA 업데이트 ID
});

await allob.start();
await allob.askCountry(ALLOB_APP.countryEndpoint);   // 실패하면 false — 앱이 다시 열릴 때 재시도
allob.track.entryWrite();

ALLOB_APP.endpoint 에는 수집 키가 들어 있어요. 수집 키는 공개 값이라 앱에 들어가도 괜찮아요.

앱 버전

version 을 넘기면 SDK 가 모든 이벤트에 앱 버전(1.2.0), 빌드 번호(34), OTA 업데이트 ID 를 실어 보내요. allob 은 이 값으로 릴리스를 그 버전을 쓰는 기기와 이어서, 같은 기간에 새 버전 기기와 옛 버전 기기를 나눠 비교하고 업데이트가 얼마나 퍼졌는지 보여 줘요(전후 비교). 넘기지 않으면 버전별 비교를 할 수 없어요(개발 빌드에서 경고가 떠요).

앱넘기는 값
Exponpx expo install expo-application(OTA 를 쓰면 expo-updates 도) 후 version: expoVersion(Application, Updates). OTA 를 안 쓰면 expoVersion(Application)
React Native(Expo 없이)version: { version: DeviceInfo.getVersion(), build: DeviceInfo.getBuildNumber() }(react-native-device-info)
Capacitorversion: () => App.getInfo()(@capacitor/app — 버전과 빌드 번호를 줘요)
  • OTA 업데이트 ID 는 OTA 로 받은 코드로 돌 때만 실어요. 앱에 들어 있던 코드로 돌면 비워요.
  • 버전은 start() 에서 한 번 읽어요. 함수를 넘기면 비동기여도 돼요.
  • 웹 이벤트와 서버 이벤트(결제 웹훅 등)에는 버전이 없어요. 서버 이벤트는 같은 기기의 앱 이벤트로 그때 쓰던 버전을 찾아요.

개발 빌드(__DEV__ — 시뮬레이터, Expo 개발 빌드)에서는 이벤트를 보내지 않아요. 설치를 확인하려고 개발 빌드에서 보내 보고 싶으면 buildApp({ ..., sendInDev: true }) 로 켜요. 이때 보낸 이벤트는 지표에 쌓이니, 확인이 끝나면 그 기기를 빼 주세요(내 기기 빼기).

웹뷰 앱

Capacitor 나 네이티브 앱 안의 웹 화면에서는 웹뷰용 연결을 써요. 웹 화면을 그대로 담아 웹 SDK 로 측정하는 앱이라면, 광고·분석 도구만 앱으로 보내는 앱 안 웹 화면에서 도구까지도 있어요. 화면이 가려지면 백그라운드로, 페이지가 닫히면 남은 이벤트를 sendBeacon 으로 보내요.

import { App } from "@capacitor/app";
import { buildApp, ALLOB_APP, webLifecycle, webNetwork, webStorage } from "./allob/app.ts";

export const allob = buildApp({
  network: webNetwork(ALLOB_APP.endpoint),
  lifecycle: webLifecycle(),
  storage: webStorage(),
  prompt: false,
  version: () => App.getInfo(),   // 앱 버전·빌드 번호
});

동의

앱 SDK 는 동의 여부가 정해지기 전에는 아무것도 보내지 않아요. 그동안 생긴 이벤트는 메모리에 최대 20개까지 보관했다가 정해지면 보내요.

  1. askCountry() 가 allob 에 이 기기의 국가를 물어요. 스키마 tracking.consent 에서 opt-in 으로 정한 지역이면 동의가 필요해요.
  2. 동의 창이 있는 앱(prompt: true)은 allob.consent.decision().ask 가 true 일 때 창을 띄우고, 사용자의 선택을 allob.consent.choose("1")(허용) 또는 allob.consent.choose("0")(거부)으로 넣어요.
  3. 동의가 필요 없는 지역이면 바로 측정을 시작해요.

allob.consent.subscribe() 로 상태가 바뀔 때마다 화면을 다시 그릴 수 있어요.

세션과 화면

const session = allob.session({
  onOpen: ({ start, first }) => allob.track.appOpen({ how: start }),   // start: cold(새로 실행) · resume(백그라운드에서 복귀), first: 이 기기 첫 실행
  screenOf: (route) => (route.startsWith("/settings") ? "settings" : "home"),
  onScreen: (screen) => allob.track.screenView({ screen }),            // 세션마다 화면 종류별로 한 번
});
session.install();
// 화면이 바뀔 때마다
session.route(currentPath);

앱이 백그라운드에 오래 있다가 돌아오면 새 세션으로 쳐요. 기준 시간은 스키마 tracking.session 에서 정해요.

결제

스토어 결제는 결제 확인을 받는 서버(RevenueCat 웹훅 등)가 기록해요. 앱은 결제 직전에 기기 ID 를 결제 서비스에 넘겨 두기만 해요.

const sid = await allob.buyerSid();   // 측정하지 않는 기기면 null
if (sid) await Purchases.setAttributes({ allob_sid: sid });

서버에서 이 값을 서버 이벤트의 sid 로 넣으면 결제가 이 기기에 이어져요. 결제 기록하기를 참고하세요. 광고 도구에도 결제를 알리려면 allob.storePurchase({ productId, transactionId, price, currency }) 를 불러요.

시험 결제(TestFlight·샌드박스·Play 테스트 트랙)는 sandbox: true 를 넣어 주세요. 광고 도구로 보내지 않아요. RevenueCat 이면 결제 정보의 환경이 PRODUCTION 이 아닐 때, StoreKit 2 면 transaction.environment 가 .production 이 아닐 때예요.

allob.storePurchase({ productId, transactionId, price, currency, sandbox: environment !== "PRODUCTION" });

광고·분석 도구(Google 애널리틱스·Meta) 붙이기

앱 설치 광고의 성과를 재려면 앱 안에 Google 애널리틱스(Firebase)와 Meta 앱 이벤트가 있어야 해요. allob 이 이 둘과 앱 추적 허용(ATT) 창, 스토어 결제 넘기기까지 하는 앱 측정 모듈(allob-measurement)을 줘요. 동의를 받기 전에는 아무것도 켜지 않고, 앱 SDK 가 켜고 끄는 때를 정해요.

  1. 스키마에 apps.measurement 를 적어요(스키마 레퍼런스).

``json "apps": { "measurement": { "google": true, "meta": { "appId": "1234567890", "clientTokenEnv": "EXPO_PUBLIC_META_CLIENT_TOKEN" }, "att": { "text": { "en": "Used to measure which ads brought you here.", "ko": "어떤 광고를 보고 왔는지 측정하는 데 쓰여요." } } } } ``

  1. allob.json 의 "sdk" 에 모듈 폴더를 적어요. 패키지 폴더라서 다른 대상과 같은 폴더에 둘 수 없고, 앱 폴더(allob.json 이 있는 폴더) 자체도 안 돼요. 앱의 package.json·ios/·android/ 와 겹치기 때문이에요. 새 폴더를 적어요. 앱의 이 파일들이 이미 있는 폴더를 적으면 --force 를 붙여도 받지 않아요.

``json "sdk": { "app": "src/allob", "native": "modules/allob-measurement" } ``

  1. allob schema push 로 올리고 allob sdk pull 로 받아요.
  2. 앱에 붙여요.
    • Expo: app.config 의 plugins 에 "./modules/allob-measurement/plugin" 을 넣고(옵션 없이) npx expo prebuild 를 실행해요. Firebase 를 켜면 expo-build-properties 에 ios.extraPods: [{ "name": "GoogleUtilities", "modular_headers": true }] 도 필요해요.
    • Capacitor: pnpm add ./modules/allob-measurement 뒤 npx cap sync 를 실행해요.
    • Google 은 앱 폴더에 GoogleService-Info.plist·google-services.json 이 있어야 켜져요. Meta 클라이언트 토큰은 스키마가 아니라 빌드 환경 변수(clientTokenEnv 에 적은 이름)에 넣어요.
  3. 앱 SDK 에 도구로 넘겨요.

```ts import { expoTools } from "./modules/allob-measurement";

const tools = expoTools({ metaClientToken: process.env.EXPO_PUBLIC_META_CLIENT_TOKEN }); export const allob = buildApp({ ..., tools, excluded: tools.isTestLabDevice() }); ```

Capacitor 면 import { capacitorTools } from "allob-measurement/capacitor" 를 쓰고 capacitorTools(registerPlugin, { metaClientToken }) 로 만들어요(isTestLabDevice() 는 await 해요).

  • 도구를 켜면 iOS 는 앱이 앞에 있을 때 앱 추적 허용 창을 한 번 띄워요. "허용"일 때만 광고 식별자를 Google·Meta 로 보내요.
  • 사용자가 거부하면 도구가 기기에 모아 둔 것도 지워요.
  • 이벤트마다 어떤 도구 이벤트로 보낼지는 스키마의 appTools 줄로 정해요.
  • 네이티브 코드가 바뀌므로 OTA 업데이트로는 나가지 않아요. 새로 빌드해서 스토어에 올려요.
  • 도구 없이 AdMob 광고만 싣는 앱은 "apps": { "measurement": { "admob": {} } } 만 적어요. 그러면 config plugin 만 받아서 SKAdNetwork 목록과 미디에이션 어댑터만 붙고, Firebase·Meta 는 앱에 들어가지 않아요.

이미 설치된 옛 앱 버전

새 앱 빌드는 allob 으로 바로 보내요. 그런데 예전 버전이 내 서버의 /api/e·/api/country 로 보내도록 만들어져 이미 설치돼 있다면, 그 버전이 사라질 때까지 내 서버가 allob 으로 넘겨 줘야 해요.

  1. allob.json 에 "proxy": "/api/e" 를 넣고, "sdk" 에 server 폴더가 있게 해요.
  2. allob sdk pull 로 받으면 server.ts 에 relayEvents·relayCountry 가 들어 있어요.
  3. allob server-key create --label relay 로 서버 키를 만들어 서버의 비밀 값 ALLOB_SERVER_KEY 에 넣어요. 없으면 allob 이 방문자 대신 내 서버의 IP·국가로 세요.
  4. 서버에 두 줄을 넣어요.

Cloudflare Workers 라면 이런 코드예요.

// Cloudflare Worker — only for app builds already installed that still send to your server. New app builds and web pages send straight to allob.
// ALLOB_SERVER_KEY: a Worker secret from `allob server-key create` — without it allob counts these events with your server's IP and country
import { relayCountry, relayEvents } from "./allob/server.ts";

const url = new URL(req.url);
if (url.pathname === "/api/e") return (await relayEvents(req, env, ctx)).response;
if (url.pathname === "/api/country") return relayCountry(req, env);

Next.js 라면 이렇게 넣어요.

// Next.js — the same for app builds already installed: app/api/e/route.ts and app/api/country/route.ts
// ALLOB_SERVER_KEY: an environment secret from `allob server-key create`
import { relayCountry, relayEvents } from "@/allob/server";

export async function POST(req: Request) {
  return (await relayEvents(req, { ALLOB_SERVER_KEY: process.env.ALLOB_SERVER_KEY })).response; // app/api/e/route.ts
}
export async function GET(req: Request) {
  return relayCountry(req, { ALLOB_SERVER_KEY: process.env.ALLOB_SERVER_KEY }); // app/api/country/route.ts
}
  • 옛 앱의 다시 보내기가 그대로 동작하도록, allob 의 응답 코드(429·413·400)와 기다릴 시간을 앱에 그대로 돌려줘요.
  • 웹 페이지는 넘기지 않아요. 웹 SDK 는 늘 allob 으로 바로 보내요.

그 밖의 함수

함수설명
setExcluded(true)내 기기나 테스트 기기를 측정에서 빼요. 이벤트를 보내지 않고 도구도 멈춰요. 기기에 기억해서 앱을 다시 켜도 빠져 있어요. setExcluded(false) 로 풀어요
deviceId()이 기기의 allob 기기 ID(없으면 null). 내 기기 빼기에서 써요
forgetDevice()로그아웃이나 계정 삭제 때 이 기기의 기록(대기 중인 이벤트, 기기 ID, 도구가 모은 정보)을 지워요
adoptSid(next)로그인으로 다른 기기 ID 와 같은 사람인 걸 알았을 때 기기 ID 를 바꿔요
setUserId(id)로그인한 사용자 ID 를 알려 줘요. 제품 안 A/B 그룹을 사용자 ID 로 정해서 다른 기기에서도 같은 그룹이에요. 이벤트에는 ID 를 싣지 않고, 피드백을 보낼 때만 보낸 사람으로 같이 가요. 로그아웃이면 null(제품 안 A/B)
measured()지금 측정 중인지 알려 줘요

사용자 의견을 앱 안에서 받으려면 feedback.send() 를 써요(앱 안 피드백 받기).

내 기기 빼기

내 폰이나 테스트 기기의 사용 기록이 지표에 섞이지 않게 하려면 두 가지를 해요.

  1. 앱에서 그 기기를 빼요. 예를 들어 설정 화면의 숨은 메뉴에서 allob.setExcluded(true) 를 불러요. 기기에 기억돼서 다음 실행부터도 보내지 않아요.
  2. 이미 쌓인 기록까지 빼려면 await allob.deviceId() 로 기기 ID 를 확인해서(빼기 전에), allob 화면의 제품 설정 › 데이터 거르기 › 앱 기기 ID 에 넣어요. allob 이 그 기기의 지난 이벤트를 지표에서 빼고, 그 기기에서 오는 이벤트도 받지 않아요.

자동 테스트 기기(Google Play 출시 전 보고서, Firebase Test Lab)도 같은 방법으로 빼요. 이 기기들은 시스템 설정 firebase.test.lab 이 "true" 예요. 앱 측정 모듈의 isTestLabDevice() 가 이 값을 읽어 줘요. iOS 는 믿을 만한 신호가 없어서 늘 false 예요.

export const allob = buildApp({ ..., tools, excluded: tools.isTestLabDevice() });   // 이번 실행만 빼요(기억하지 않아요)

전송 방식

앱은 allob 의 응답 코드(400·413·429)에 맞춰 동작해요. 보내지 못한 이벤트는 기기에 보관했다가 다시 보내고, 서버가 기다리라고 하면 기다려요. 앱이 백그라운드로 가거나 닫힐 때 남은 이벤트를 보내요. allob 이 원격으로 전송을 끄라고 응답하면 allob 으로 보내는 것만 멈춰요.

전송은 allob 화면의 제품 › 설정 › 측정에서 「이벤트 수집」을 끄거나 allob ingest pause 로 원격으로 끌 수 있어요. 끄면 1분 안에 allob 이 이 제품의 이벤트를 받지 않고, 앱은 다음 실행부터 allob 으로 보내는 것을 멈추고 모아 둔 이벤트를 지워요. Meta·Google 같은 다른 측정 도구로 보내는 것과 서버 이벤트는 그대로예요. 다시 켜려면 allob ingest resume 을 실행해요.

마크다운으로 보기 · llms.txt · 개인정보 처리방침 · 이용약관