# Elice CDK 사용 가이드

이 가이드는 Elice Contents Development Kit (`@eliceio/cdk`)을 사용하여 Elice 플랫폼과 통합되는 외부 콘텐츠를 개발하는 방법을 설명합니다.

## 목차

- [설치](#설치)
- [초기화](#초기화)
- [계정 및 메타데이터](#계정-및-메타데이터)
- [점수 전송](#점수-전송)
- [키-값 저장소](#키-값-저장소)
- [수업자료 키-값 저장소](#수업자료-키-값-저장소)
- [파일 저장소](#파일-저장소)
- [AI 채팅](#ai-채팅)
- [튜터링](#튜터링)
- [내비게이션](#내비게이션)
- [번역](#번역)

---

## 설치

```bash
# npm
npm install @eliceio/cdk

# yarn
yarn add @eliceio/cdk
```

---

## 초기화

SDK 기능을 사용하기 전에 SDK 인스턴스를 초기화해야 합니다. `init()` 메서드는 URL 쿼리 문자열에서 필요한 파라미터를 읽어옵니다.

### 필수 쿼리 파라미터

Elice에 임베드될 때 콘텐츠 URL은 다음 쿼리 파라미터를 전달받습니다:

| 파라미터     | 필수 여부 | 설명                              |
| ------------ | --------- | --------------------------------- |
| `extToken`   | 예        | 인증용 JWT 토큰                   |
| `courseId`   | 예        | 현재 전역 ID                      |
| `materialId` | 예        | 현재 수업자료(강의 페이지) ID         |
| `locale`     | 아니오    | 사용자 로케일 (기본값: `ko`)      |
| `parentUrl`  | 아니오    | 부모 프레임 URL                   |

### 기본 초기화

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();

// SDK 초기화 (window.location.search에서 쿼리 파라미터 읽기)
await sdk.init();

console.log('초기화 완료:', sdk.initialized); // true
```

### 커스텀 초기화

애플리케이션이 커스텀 라우팅을 사용하거나 쿼리 파라미터를 다른 곳에 저장하는 경우, `search` 문자열을 직접 전달할 수 있습니다:

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();

// 쿼리 문자열 직접 전달
await sdk.init({
  search: '?extToken=...&courseId=123&materialId=456',
});
```

### 설정 옵션

SDK 인스턴스 생성 시 API 엔드포인트를 커스터마이징할 수 있습니다:

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK({
  // 커스텀 API 기본 URL (기본값: 'https://api-external-contents.elice.io')
  baseUrl: 'https://custom-api.example.com',
  // 커스텀 ESP 프록시 URL (기본값: 'https://api-esp-proxy.elice.io')
  espProxyBaseUrl: 'https://custom-proxy.example.com',
});

await sdk.init();
```

---

## 계정 및 메타데이터

초기화 후 사용자 계정 정보와 콘텐츠 메타데이터에 접근할 수 있습니다.

### 계정 정보

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

const account = sdk.account;
// {
//   uid: 12345,
//   fullname: 'John Doe',
//   accountId: '...',        // deprecated, uid 사용 권장
//   __isTutorAccount: false  // 튜터 모드로 접근 시 true
// }
```

### 콘텐츠 메타데이터

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

const metadata = sdk.metadata;
// { courseId: 123, materialId: 456 }
```

### 로케일

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

const locale = sdk.locale;
// 'ko' | 'en' | 'ja' | 'th' (기본값: 'ko')
```

---

## 점수 전송

사용자의 완료 점수(0–100)를 Elice 플랫폼에 전송합니다.

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

// 만점으로 완료 보고
await sdk.sendScore({ score: 100 });

// 부분 완료 보고
await sdk.sendScore({ score: 75 });
```

**제약 사항:**
- 점수는 0에서 100 사이의 숫자여야 합니다.
- SDK가 초기화되지 않은 경우 `EliceCDKError`가 발생합니다.

---

## 키-값 저장소

사용자별 데이터를 저장하고 조회합니다. 키는 자동으로 현재 수업자료 또는 전역 범위로 지정됩니다.

### 수업자료 범위 저장소

현재 사용자 + 수업자료 조합으로 데이터가 범위 지정됩니다.

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

// 데이터 저장
await sdk.kvstore.post({
  key: 'userProgress',
  value: { step: 3, completed: false },
});

// 데이터 조회
const progress = await sdk.kvstore.get({ key: 'userProgress' });
// { step: 3, completed: false }

// 점 표기법으로 중첩 값 접근
const step = await sdk.kvstore.get({ key: 'userProgress.step' });
// 3

// 데이터 삭제
await sdk.kvstore.delete('userProgress');
```

### 전역 범위 저장소 (글로벌)

현재 사용자 + 전역 조합으로 데이터가 범위 지정되며, 전역 내 모든 수업자료에서 공유됩니다.

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

// 전역 전체 데이터 저장
await sdk.kvstore.postGlobal({
  key: 'courseSettings',
  value: { theme: 'dark', fontSize: 16 },
});

// 전역 전체 데이터 조회
const settings = await sdk.kvstore.getGlobal({ key: 'courseSettings' });

// 전역 전체 데이터 삭제
await sdk.kvstore.deleteGlobal('courseSettings');
```

**키 명명 규칙:**
- camelCase 형식을 사용합니다.
- 영문자와 숫자만 허용됩니다 (`[a-zA-Z0-9]+`).

**지원되는 값 타입:**
- 기본 타입: `string`, `number`, `boolean`
- 객체 및 배열 (키는 camelCase여야 함)

---

## 수업자료 키-값 저장소

수업자료 레벨에서 데이터를 저장합니다 (사용자별로 범위 지정되지 않음). 공유 콘텐츠 상태에 유용합니다.

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

// 수업자료 레벨 데이터 저장
await sdk.materialKvstore.post({
  key: 'sharedConfig',
  value: { maxAttempts: 3 },
});

// 수업자료 레벨 데이터 조회
const config = await sdk.materialKvstore.get({ key: 'sharedConfig' });

// 수업자료 레벨 데이터 삭제
await sdk.materialKvstore.delete('sharedConfig');
```

---

## 파일 저장소

사용자 파일을 업로드하고 관리합니다. 파일은 Azure Blob Storage에 저장되고 KV store를 통해 추적됩니다.

### 파일 업로드

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

// input 요소에서 파일 가져오기
const fileInput = document.querySelector<HTMLInputElement>('#file-input');
const file = fileInput?.files?.[0];

if (file) {
  await sdk.filestore.post('myDocument', file);
}
```

### 파일 조회

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

const fileData = await sdk.filestore.get('myDocument');
if (fileData) {
  console.log(fileData);
  // {
  //   name: 'document.pdf',
  //   size: 102400,
  //   mime: 'application/pdf',
  //   url: 'https://...'  // 다운로드 URL
  // }
}
```

### 파일 삭제

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

await sdk.filestore.delete('myDocument');
```

**제약 사항:**
- 파일은 `File` 인스턴스여야 합니다.
- 최대 파일 크기: **50 MB**

---

## AI 채팅

채팅 기반 기능을 위해 Elice AI와 상호작용합니다.

### 프롬프트 전송

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

// 프롬프트 전송 (새 세션 자동 생성)
const { sessionId, responseMessageContent } = await sdk.ai.chat.prompt(
  '대한민국의 수도는 어디인가요?'
);

console.log(responseMessageContent); // '서울은 대한민국의 수도입니다...'
console.log(sessionId); // 채팅 세션의 UUID
```

### 시스템 지시문 사용

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

const response = await sdk.ai.chat.prompt('프로그래밍에서 변수를 설명해주세요', {
  systemInstruction: '10살 아이에게 설명하듯이 개념을 설명해주세요.',
});
```

### 기존 세션 불러오기

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

// 이전에 저장된 세션 불러오기
const { sessionId, messages } = await sdk.ai.chat.load(
  '550e8400-e29b-41d4-a716-446655440000'
);

// 대화 계속하기
await sdk.ai.chat.prompt('그것에 대해 더 알려주세요.');
```

### 채팅 이벤트 구독

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

const { unsubscribe } = sdk.ai.chat.subscribe((event) => {
  switch (event.type) {
    case 'comment':
      console.log('새 메시지:', event.payload);
      break;
    case 'load':
      console.log('세션 로드됨:', event.payload.sessionId);
      break;
    case 'reset':
      console.log('세션 초기화됨');
      break;
    case 'clear':
      console.log('세션 삭제됨:', event.payload.sessionId);
      break;
  }
});

// 완료 시 구독 해제
unsubscribe();
```

### 세션 초기화 및 삭제

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

// 로컬 상태 초기화 (메모리의 세션 ID와 메시지)
sdk.ai.chat.reset();

// 서버 저장소에서 세션 삭제
await sdk.ai.chat.clear('550e8400-e29b-41d4-a716-446655440000');
```

### 현재 세션 데이터 접근

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

// 현재 세션 ID (활성 세션이 없으면 null)
const sessionId = sdk.ai.chat.sessionId;

// 현재 메시지 배열
const messages = sdk.ai.chat.messages;
// [
//   { role: 'system', content: '...', ts: 1234567890 },
//   { role: 'user', content: '안녕하세요', ts: 1234567891 },
//   { role: 'assistant', content: '안녕하세요!', ts: 1234567892 },
// ]
```

**제약 사항:**
- 세션당 최대 100개 메시지

---

## 튜터링

튜터로 콘텐츠를 볼 때 튜터링 전용 기능에 접근합니다.

### 튜터링 모드 확인

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

if (sdk.tutoring.isEnabled) {
  console.log('튜터 모드가 활성화되었습니다');
}
```

### 학생 목록 조회

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

const students = await sdk.tutoring.getAccountList({
  offset: 0,
  count: 10,
});
// [{ uid: 123, fullname: '학생 A' }, ...]
```

### 학생의 KV Store 접근

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

const studentProgress = await sdk.tutoring.getAccountKvstore({
  uid: 12345,
  key: 'userProgress',
});
```

### 학생의 File Store 접근

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

const studentFile = await sdk.tutoring.getAccountFilestore({
  uid: 12345,
  key: 'submission',
});

if (studentFile) {
  console.log('파일 URL:', studentFile.url);
}
```

### KV Store 값 일괄 조회

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

const results = await sdk.tutoring.getAccountKvstoreList({
  key: 'userProgress',
  filterUids: [123, 456, 789], // 최대 10개 UID
});
// [{ uid: 123, value: {...} }, { uid: 456, value: {...} }, ...]
```

---

## 내비게이션

Elice 플랫폼 내에서 강의 페이지 간 이동합니다. 콘텐츠가 iframe에 임베드되어 있어야 합니다.

### 이전/다음 페이지로 이동

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

// 이전 페이지로 이동
sdk.navigation.previous();

// 다음 페이지로 이동
sdk.navigation.next();
```

### 내비게이션 가능 여부 확인

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

try {
  const hasPrevious = await sdk.navigation.getHasPrevious();
  const hasNext = await sdk.navigation.getHasNext();

  console.log('뒤로 이동 가능:', hasPrevious);
  console.log('앞으로 이동 가능:', hasNext);
} catch (error) {
  // iframe 내부가 아닌 경우 1초 후 타임아웃
  console.log('내비게이션 상태를 확인할 수 없습니다');
}
```

**참고:** 내비게이션 메서드는 `postMessage`를 통해 부모 프레임과 통신합니다. 콘텐츠가 Elice 플랫폼에 임베드되어 있지 않으면 1초 후 타임아웃됩니다.

---

## 번역

Google Translate를 사용하여 콘텐츠 자동 번역을 활성화합니다.

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

// 번역 언어 선택기 표시
await sdk.translation.displayAutoLanguageOptions();
```

이 기능은 페이지에 언어 선택기를 추가하여 사용자가 콘텐츠를 영어, 일본어 또는 태국어로 번역할 수 있게 합니다. 대부분의 콘텐츠가 한국어로 작성되어 있으므로 한국어는 제외됩니다.

**참고:** 이 기능은 브라우저 환경에서만 작동합니다.
