> ## Documentation Index
> Fetch the complete documentation index at: https://docs.facebetter.net/llms.txt
> Use this file to discover all available pages before exploring further.

# 인증 및 라이선스

> Facebetter SDK 2.0 온라인 인증과 오프라인 라이선스

SDK **2.0**은 **라이선스 토큰**으로 인증합니다. 네이티브 플랫폼은 온라인 인증에 `appId` + `appKey`도 사용할 수 있습니다. Web에서는 프론트엔드 JavaScript에 `appKey`를 넣지 마세요.

<Tip>
  먼저 [구독](./enable-service)을 완료하세요. 대시보드에서 앱 식별자(Bundle ID / 패키지 이름 / 도메인)를 바인딩하고 요금제를 활성화합니다.
</Tip>

## 네이티브 (Android / iOS / macOS / Windows / Linux / Flutter)

엔진을 만들 때 다음 중 하나를 전달합니다.

| 모드    | 설정 필드              | 사용 시점                                            |
| ----- | ------------------ | ------------------------------------------------ |
| 온라인   | `appId` + `appKey` | 기기가 `/facebetter/v2/auth`에 도달할 수 있을 때            |
| Token | `licenseToken`     | 라이선스 토큰 문자열, `{token}` JSON, 또는 네이티브 오프라인 `.lic` |

**우선순위**: `licenseToken`이 비어 있지 않으면 SDK는 해당 토큰을 로컬에서 검증하며, `appId` / `appKey`로 네트워크를 호출하지 **않습니다**.

<Tip>
  권장 방식은 Web과 같습니다. `appId` / `appKey`는 **서버**에 두고, 수명이 짧은 `licenseToken`을 발급해 앱에 전달하세요. 서명 요청의 `platform`은 클라이언트와 일치해야 합니다(`ios` / `android` / `macos` / `windows` / `linux`). 아래 [Web → 서버 프록시](#server-proxy)를 참고하세요. `appId` + `appKey` 직접 전달과 오프라인 `.lic`도 계속 지원됩니다.
</Tip>

**iOS / macOS**

```objc theme={null}
FBEngineConfig *config = [[FBEngineConfig alloc] init];
config.licenseToken = @"/* license token, {token} JSON, or .lic contents */";
self.beautyEffectEngine = [FBBeautyEffectEngine createEngineWithConfig:config];
```

**Android**

```java theme={null}
BeautyEffectEngine.EngineConfig config = new BeautyEffectEngine.EngineConfig();
config.licenseToken = "/* license token, {token} JSON, or .lic contents */";
mBeautyEngine = new BeautyEffectEngine(this, config);
```

**C++ (Windows / Linux)**

```cpp theme={null}
facebetter::EngineConfig config;
config.license_token = "/* license token, {token} JSON, or .lic contents */";
config.resource_path = "/path/to/resource.fbd";
auto engine = facebetter::BeautyEffectEngine::Create(config);
```

**Flutter**

```dart theme={null}
final engine = await FBEngine.create(
  FBEngineConfig(licenseToken: '/* license token, {token} JSON, or .lic contents */'),
);
```

네이티브 플랫폼의 온라인 대안:

```java theme={null}
config.appId = "your appId";
config.appKey = "your appKey";
```

오프라인 `.lic`는 소스에 하드코딩하지 말고 앱 에셋으로 넣고 런타임에 로드하는 것을 권장합니다. 발급 방법은 아래 [오프라인 라이선스](#offline-license)를 참고하세요.

## Web

브라우저 SDK에는 `appId` / `appKey` 필드가 **없습니다**. 엔진은 `licenseToken`만 받으며 로컬에서 검증합니다. `init()` **전에** **서버**에서 토큰을 가져오세요.

### 권장 흐름

1. 대시보드에서 앱을 만들고 Web **도메인**을 바인딩한 뒤 `appId` / `appKey`를 복사합니다([구독](./enable-service) 참고)
2. 키는 **백엔드**에 두고, 엔드포인트(예: `POST /api/facebetter/auth`)를 제공합니다
3. 백엔드는 `https://facebetter.pixpark.net/facebetter/v2/auth`에 서명 요청을 보내고 **원본 응답 본문**을 페이지에 반환합니다
4. 해당 문자열을 `EngineConfig.licenseToken`에 전달한 뒤 `init()`을 호출합니다

공식 데모(`demo/web/react`)도 같은 패턴입니다. 프론트엔드는 `src/fetchLicenseToken.js`, 서버 프록시는 `api/facebetter/auth.js`입니다.

<Warning>
  프론트엔드 번들에 `appKey`를 넣지 마세요. Web은 오프라인 `.lic` 파일을 지원하지 **않습니다**. 온라인 토큰은 수 분 안에 만료되므로, 엔진을 다시 만들기 전에 새 토큰을 받으세요.
</Warning>

### 프론트엔드

```javascript theme={null}
import { BeautyEffectEngine, EngineConfig } from 'facebetter';

// Body may be a bare token, or the upstream JSON text (with a token field)
const licenseToken = await fetch('/api/facebetter/auth', {
  method: 'POST',
}).then((r) => {
  if (!r.ok) throw new Error(`auth failed: ${r.status}`);
  return r.text();
});

const engine = new BeautyEffectEngine(new EngineConfig({ licenseToken }));
await engine.init();
```

<h3 id="server-proxy">
  서버 프록시 (Node 예시)
</h3>

`FB_APP_ID` / `FB_APP_KEY`를 환경 변수에 저장하세요. Express / Fastify / 서버리스 함수에 그대로 넣을 수 있습니다.

```javascript theme={null}
import { createHmac, randomBytes } from 'node:crypto';

const AUTH_URL = 'https://facebetter.pixpark.net/facebetter/v2/auth';

app.post('/api/facebetter/auth', async (req, res) => {
  const appId = process.env.FB_APP_ID;
  const appKey = process.env.FB_APP_KEY;
  if (!appId || !appKey) {
    res.status(500).json({ error: 'FB_APP_ID / FB_APP_KEY not configured' });
    return;
  }

  const nonce = randomBytes(16).toString('hex');
  const timestamp = Math.floor(Date.now() / 1000);
  const platform = 'web';
  // Sign string: v2|{app_id}|{timestamp}|{nonce}|{platform}
  const payload = `v2|${appId}|${timestamp}|${nonce}|${platform}`;
  const hmac = createHmac('sha256', appKey).update(payload).digest('hex');

  const upstream = await fetch(AUTH_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      app_id: appId,
      hmac_signature: hmac,
      timestamp,
      nonce,
      platform,
      user_agent: req.headers['user-agent'] || '',
    }),
  });

  // Forward as-is for EngineConfig.licenseToken
  res.status(upstream.status).send(await upstream.text());
});
```

다른 언어에서도 단계는 같습니다. 서명 문자열 구성 → HMAC-SHA256(hex) → 업스트림 POST → 본문을 프론트엔드에 전달. 실행 가능한 참고 구현: [GitHub Demo `api/facebetter/auth.js`](https://github.com/pixpark/facebetter-sdk/blob/main/demo/web/react/api/facebetter/auth.js).

### 업스트림 요청 필드

| 필드               | 설명                                                              |
| ---------------- | --------------------------------------------------------------- |
| `app_id`         | 대시보드 AppID                                                      |
| `hmac_signature` | `v2\|{app_id}\|{timestamp}\|{nonce}\|web`의 HMAC-SHA256, hex 인코딩 |
| `timestamp`      | Unix 초                                                          |
| `nonce`          | 난수 문자열(16바이트 이상 hex 권장)                                         |
| `platform`       | 항상 `web`                                                        |
| `user_agent`     | 선택. 가능하면 브라우저 UA를 전달합니다                                         |

성공 시 업스트림은 JSON(`token` 필드 포함)을 반환합니다. 응답 텍스트를 그대로 전달하거나 `token` 문자열만 추출해도 됩니다. SDK는 둘 다 받습니다.

<h2 id="offline-license">
  오프라인 라이선스 (네이티브)
</h2>

대시보드는 오프라인 `.lic` 셀프 다운로드를 **더 이상** 제공하지 않습니다. 오프라인 라이선스가 필요하면 영업 또는 관리자에게 문의하세요.

* 이메일: [hello@facebetter.net](mailto:hello@facebetter.net) (제목 예시: `[Business Partnership] Offline license`)
* 기타 방법: [문의하기](https://facebetter.net/ko/contact)

파일을 받은 뒤 **전체 내용**을 `licenseToken` / `license_token`으로 전달하세요. 유효 기간은 현재 구독과 같습니다. 갱신하거나 요금제를 바꾼 뒤에는 새 파일을 요청해 앱에 교체하세요.

### 참고

* 오프라인 라이선스는 대시보드에 설정한 앱 식별자에 바인딩됩니다. 불일치하면 검증이 실패합니다.
* 만료되지 않음: 기능은 구독 요금제를 따릅니다.
* 만료됨: 엔진은 초기화할 수 있지만, 기능은 **Free** 요금제로 떨어집니다(워터마크 체험, 키포인트 콜백 없음). 갱신 후 새 라이선스를 요청하세요.
* Web은 오프라인 라이선스를 사용할 수 없으며, 온라인 v2 인증 경로를 완료해야 합니다.

## 엔진 이벤트

`onEngineEvent` / Flutter `engine.events`로 라이선스와 초기화 결과를 수신합니다.

| 코드  | 이름                           | 의미                |
| --- | ---------------------------- | ----------------- |
| 0   | `LICENSE_VALIDATION_SUCCESS` | 토큰 / 온라인 인증 성공    |
| 1   | `LICENSE_VALIDATION_FAILED`  | 인증 실패(message 참고) |
| 100 | `INITIALIZATION_COMPLETE`    | 엔진 준비 완료          |
| 101 | `INITIALIZATION_FAILED`      | 엔진 초기화 실패         |
