Nuxt — @yetdapay/license-nuxt
핵심 요약
Nuxt 3의 h3 서버 라우트에 라이선스 권한 검사를 추가합니다. 비밀 키와 권한 결정은 서버 전용 파일에 두세요.
서버 헬퍼 예제를 먼저 적용한 뒤 클라이언트 컨트롤러를 연결하고, 권한 거부 상황까지 확인하세요.
sdk/license/nuxt/ — Nuxt 3 어댑터. @yetdapay/license-server를 h3 친화적 가드로 감싸고 Nuxt 외부에서도 동작하는 두 개의 프레임워크 비종속 코어(/core와 /client)를 제공합니다.
설정
js
// server/utils/license.ts — 서버 전용
import { createLicenseServer, createSqliteStore } from '@yetdapay/license-server';
import { createNuxtLicense } from '@yetdapay/license-nuxt';
const server = createLicenseServer({
store: createSqliteStore({ file: './.data/license.db' }),
keys: { load: async () => JSON.parse(process.env.DEVPAY_LICENSE_KEYS) },
issuer: 'devpay',
audience: 'app:my-product',
entitlementTtlSec: 120,
});
export const license = createNuxtLicense({ server });1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
js
// server/api/premium/convert.post.ts
export default license.defineLicenseEventHandler(
{ feature: 'premium_convert' },
async (event, context) => {
const body = await license.readJsonBody(event);
return { result: runPaidLogic(body.input, context) };
}
);1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
공개 API — createNuxtLicense({server, maxBodyBytes?, defaultFeature?})
| 멤버 | 동작 |
|---|---|
requireLicense(event, {feature?, resource?}) | 검증; LicenseContext 반환; 거부 시 throw. |
authorize(event) | 기능 게이트 없이 검증. |
defineLicenseEventHandler(options, handler) | 검증 + 핸들러 실행; 거부는 JSON Response가 됨. |
readJsonBody(event) | 가드가 이미 읽은 원시 바이트 파싱(event._requestBody에 캐시됨). |
descriptorOfEvent(event) | {headers, method, requestTarget, rawBody} 추출. |
errorDescriptor(error) / licenseErrorResponse(error) | LicenseError → {status, body} / 웹 표준 Response. |
guard, server | 하위 인스턴스. |
options.resource는 함수 (event, descriptor) => resource일 수 있습니다 — 소유권 훅에 리소스 id를 공급하는 데 사용됩니다.
프레임워크 비종속 코어
js
// @yetdapay/license-nuxt/core — 임의 런타임 (Express/Fastify/Workers/node:http)
import { createLicenseGuard } from '@yetdapay/license-nuxt/core';
const guard = createLicenseGuard({ server, maxBodyBytes: 64 * 1024 });
const ctx = await guard.requireLicense(
{ headers, method, requestTarget, rawBody },
{ feature: 'x' }
);1
2
3
4
5
6
7
2
3
4
5
6
7
js
// @yetdapay/license-nuxt/client — 클라이언트 측 상태 컨트롤러
import { createLicenseClient } from '@yetdapay/license-web';
import { createLicenseController } from '@yetdapay/license-nuxt/client';
const controller = createLicenseController({ client });
controller.subscribe((snapshot) => render(snapshot));
await controller.activate('NG-…-…');
await controller.call('/api/premium/convert', { method: 'POST', body: '…' });1
2
3
4
5
6
7
2
3
4
5
6
7
스냅샷: {status, entitled, features, expiresAt, productId, error}. 컨트롤러는 의도적으로 vue를 import하지 않습니다 — file: 링크된 패키지가 두 번째 Vue 인스턴스를 해석하면 반응성이 조용히 깨집니다; 앱에서 한 번 감싸세요(예: useLicense() composable).
Body 처리 및 오류 계약
- 가드는 원시 body 바이트를 먼저 읽어 증명의
h(body 해시)와 비교합니다. 핸들러에서는license.readJsonBody(event)를 사용하세요 — plainreadBody()도 보통 동작하지만readJsonBody가 바이트 보존 경로입니다. maxBodyBytes(기본 64 KiB) 초과 body →413 BODY_TOO_LARGE.- 거부 body는
@yetdapay/license-server오류 계약을 따릅니다:{"code":"FORBIDDEN","message":"…"}.errorDescriptor는instanceof대신 오류를 duck-type 검사(code+status)합니다 — SDK의 두 번째 복사본을 번들링한 호스트가 403을 500으로 바꾸는 것을 방지합니다. 알 수 없는 오류는 항상500 INTERNAL로 일반화됩니다.
SSR 노트
createNuxtLicense는 서버 전용 —server/밖에서 import하면 서버 코드가 클라이언트 번들로 새어 나갑니다.- 전체 API 서브트리를 가드하려면
server/middleware/license.ts에서requireLicense를 호출하고 실패 시licenseErrorResponse(error)를 반환하세요. - 정적/SSR 페이지 렌더링은 라이선싱과 무관합니다 — 보호 대상은 페이지가 아닌 서버 API 라우트입니다.