OPWS Developers

Partner SDK API 레퍼런스 안내

이 문서는 Open Echo Partner SDK 공개 표면(공개적으로 지원되는 함수·타입·속성)의 레퍼런스입니다.

이 킷에 동봉된 SDK 버전은 0.7.0 이며, 아래 내용은 이 버전을 기준으로 작성되었습니다.

사용법과 예제는 guide 문서에서 안내합니다.

SDK 버전 간 호환 여부는 COMPATIBILITY 문서에서 안내합니다.

이 문서에 이름이 없는 함수·타입·속성은 공개 계약이 아니며, 예고 없이 추가·변경·제거될 수 있습니다.

Partner SDK 공개 인터페이스 계약 (API Contract)

대상 SDK 버전: 0.7.0 · 패키지 @openecho/partner-sdk · preview (0.x)


0. 이 문서의 지위

이 문서는 Partner SDK 공개 인터페이스의 규범(normative) 문서다. SDK 는 이 문서를 기준으로 버전별로 유지된다.

  • 공개 계약은 이 문서의 표면 정의 절과 버전 이력이 정의하며, 비공개로 명시된 표면은 계약이 아니다. 코드에서 도달 가능하더라도 그 절들에 없으면 공개 계약이 아니며, 비공개 표면으로 열거된 항목은 예고 없이 변경될 수 있다.
  • 사용법·튜토리얼은 README.mddocs/partner-kit/guide/(11편)가 담당한다 — 그 문서들은 설명적(informative)이며, 충돌 시 이 문서가 우선한다.

버전 규칙 (0.x preview)

변경 종류버전의무
표면 제거·시그니처 비호환 변경 (breaking)minor ↑이 문서 버전 이력 기록 + CHANGELOG 명시 + COMPATIBILITY.md 매트릭스 갱신
표면 추가 (additive)minor ↑이 문서에 X.Y.Z+ 주석으로 도입 버전 표기
필수 필드 → XOR-optional 완화additive 로 판정하되 3조건 전부 충족 시에만: ① 기존 타입-유효 호출이 전수 그대로 유효 ② 런타임 불변(직렬화·idempotency·서버 검증 결과 동일) ③ 구 SDK 가 동일 응답 수신. 하나라도 깨지면 breaking선례: 0.2.0 CreateTemplateInput(버전 이력 참조)
동작 수정 (표면 불변)patch ↑CHANGELOG 만

GA(1.0.0) 이후 breaking 은 major 로 승격된다. preview 기간에도 breaking 은 버전 이력에 반드시 기록한다 — 서버 min-version 게이트(§5 SdkVersionUnsupported)와 킷 호환 매트릭스가 이 이력에 의존한다.

표기 규약

  • 항목 옆 0.2.0+ = 그 버전에 도입. 무표기 = 0.1.0(최초 릴리스)부터 존재.
  • ?(optional) 필드 중 "서버 확장" 주석이 있는 것은 구버전 서버가 내려주지 않으면 undefined — 소비자는 undefined 를 처리해야 한다 (SDK 가 아니라 서버 버전에 의존하는 필드).

1. 진입점

1.1 npm (ESM / CJS) — 번들러 통합

ts
import { createClient, SDK_VERSION } from "@openecho/partner-sdk";
const oe = createClient(config);   // OpenEchoClient
  • MIC 구현은 mic.start()/mic.preload() 시 동적 청크(@openecho/partner-sdk/mic)로 로드된다 — core 번들에 실시간 미디어 엔진 없음.

1.2 UMD (self-host / CDN) — script 태그 통합

html
<script src="/assets/openecho-sdk.<version>.umd.js"></script>
<script> const oe = window.OpenEcho.createClient(config); </script>
  • 전역 네임스페이스 window.OpenEcho 에 npm 표면과 동일한 export 가 실린다 (createClient·SDK_VERSION·에러 클래스·mapError·ERROR_BIJECTION).
  • MIC 포함 all-in-one 단일 파일 — 동적 청크 없음. <script> 는 classic 으로 로드해야 한다(type="module" 금지).

1.3 createClient(config: ClientConfig): OpenEchoClient

ClientConfig 필드타입필수설명
baseUrlstringOpen Echo API 호스트. trailing slash 는 정규화 제거. 누락 시 즉시 Error throw
publicKeystring도메인 잠금 공개 키 (pk_live_…/pk_test_…). 누락 시 즉시 Error throw
fetchtypeof fetchfetch 주입(테스트·특수 런타임). 미주입 + 전역 fetch 부재 시 즉시 Error throw
micChunkUrlstringMIC 청크 URL 수동 지정 (npm 전용 — UMD 는 MIC inline)
scriptNoncestringCSP nonce — MIC 청크 <script> 주입 시 사용 (npm 전용)

1.4 SDK_VERSION: string

package.json 버전과 일치하는 상수 (테스트로 강제). 0.2.1 이상에서만 신뢰 가능 — 0.2.0 배포본에 이전 값이 남은 결함이 있었다 (버전 이력 참조).

1.5 프로토콜 동작 (표면 아님, 계약임)

  • 0.3.0+ 모든 요청(REST·SSE·MIC lease)에 X-OpenEcho-Sdk-Version 요청 헤더 자동 부착. 서버 min-version 게이트가 이를 판독하며, 미달 시 HTTP 426 → SdkVersionUnsupported (§5).
  • 0.3.0+ 서버 응답의 X-OpenEcho-Sdk-Warn-Below 헤더 감지 시 console.warn 1회 (차단 예고).

2. 클라이언트 표면 전체 트리

OpenEchoClientcreateClient() 반환 타입. _ 접두 멤버는 타입에서 제외되며 비공개 표면이다.

oe.ping()                                → Promise<PingResult>
oe.targets.list()                        → Promise<DeviceView[]>
oe.groups.create(req)                    → Promise<GroupView>
oe.groups.list(opts?)             0.6.0+ → Promise<GroupView[]>
oe.groups.get(handle)                    → Promise<GroupView>
oe.groups.addMembers(handle, deviceHandles)    → Promise<void>
oe.groups.removeMembers(handle, deviceHandles) → Promise<void>
oe.groups.members(handle)                → Promise<DeviceView[]>
oe.groups.delete(handle)                 → Promise<void>
oe.contents.list(opts?)           0.6.0+ → Promise<ContentView[]>
oe.contents.get(handle)                  → Promise<ContentView>
oe.contents.create(req)          0.2.0+  → Promise<ContentView>
oe.contents.update(handle, req)  0.2.0+  → Promise<ContentView>
oe.templates.create(req)                 → Promise<TemplateView>
oe.templates.list()                      → Promise<TemplateView[]>
oe.templates.get(handle)                 → Promise<TemplateView>
oe.templates.update(handle, req)         → Promise<TemplateView>
oe.templates.delete(handle)              → Promise<void>
oe.schedules.create(req)                 → Promise<ScheduleView>
oe.schedules.list(opts?)          0.6.0+ → Promise<ScheduleView[]>
oe.schedules.get(handle)                 → Promise<ScheduleView>
oe.schedules.update(handle, req)         → Promise<ScheduleView>
oe.schedules.delete(handle)              → Promise<void>
oe.history.list(opts?)            0.6.0+ → Promise<Page<HistoryView>>
oe.history.get(sessionId)                → Promise<HistoryView>
oe.languages.list()              0.2.0+  → Promise<LanguageOption[]>
oe.broadcast.tts.send(input)             → Promise<BroadcastReceipt>
oe.broadcast.tts.stop(sessionId) 0.2.0+  → Promise<void>
oe.broadcast.tts.fromContent(input) 0.2.0+ → Promise<BroadcastReceipt>
oe.broadcast.subscribe(sessionId, handlers) → { close(): void }
oe.broadcast.mic.isSupported()           → boolean
oe.broadcast.mic.preload()               → Promise<void>
oe.broadcast.mic.start(input)            → Promise<MicSession>

제거된 표면: contents.clone(sourceHandle) — 0.1.0 에 존재, 0.2.0 에서 제거 (버전 이력 참조).


3. 네임스페이스 레퍼런스

모든 메서드는 실패 시 OpenEchoError 하위 클래스를 throw 한다 (§5). SSE 만 예외적으로 onError 콜백으로 전달한다.

3.1 ping()

GET /partner/v1/ping — 키·origin 유효성 즉시 확인. 반환 PingResult { appName, appId, origin }.

3.2 targets — 노출 디바이스 읽기 전용

  • list() — 앱에 노출된 디바이스 목록. 방송 대상 선택의 소스.
  • 전체 위임(FULL_DELEGATION) 앱은 명시 노출 없이도 현장의 사용 가능한 디바이스가 자동으로 목록에 나타난다. handle 은 불투명 문자열이며 접두(tgt_/atg_)에 의미를 두지 말 것. 목록은 조회 시점 스냅샷이다.
  • 자동 노출 handle(targets.list()가 반환하는 것 포함)은 방송 대상 지정(broadcast.*)·그룹 멤버 구성·재방송·**템플릿 개별 디바이스 지정(deviceHandles)**에 모두 사용할 수 있다(전 경로 관통).

3.3 groups — 앱 소유 디바이스 그룹 CRUD (update 없음)

  • create({ name, description? })description?0.2.0+
  • list({ search? }?)search 는 그룹 이름 부분일치(대소문자무시), 미지정 시 전량. 필터 파라미터는 0.6.0+ — 이 필터를 포함한 api-server 릴리스 이후에만 서버가 적용한다(구서버는 파라미터를 무시하고 전량 반환).
  • get(handle) · members(handle)
  • addMembers(handle, deviceHandles[]) · removeMembers(handle, deviceHandles[])
  • delete(handle) — 논리 삭제. 그룹 이름 변경(update)은 제공하지 않음.

3.4 contents — 방송 컨텐츠 (노출분 read + 자작 create)

  • list({ search? }?) · get(handle) — SITE 노출분 + 자작분. 노출분의 bodynull(원문 비노출 정책). search 는 컨텐츠 제목 부분일치(대소문자무시), 미지정 시 전량. 필터 파라미터는 0.6.0+ — 이 필터를 포함한 api-server 릴리스 이후에만 서버가 적용한다(구서버는 파라미터를 무시하고 전량 반환).
  • create({ title, text, languages[] }) 0.2.0+ — 자작 컨텐츠. 원문은 한국어, 워커가 번역+합성.
  • update(handle, { title, addLanguages? }) 0.2.0+ — 제목 변경 + 언어 추가만(add-only, 원문·기존 언어 불변). 자작분만 가능.

3.5 templates — 방송 템플릿 CRUD

  • create(CreateTemplateInput) — 대상은 groupHandle(그룹) 또는 deviceHandles(개별 다중) 중 정확히 하나 (서버 XOR 검증 400). deviceHandles 모드는 0.2.0+ (0.1.0 은 groupHandle 필수 단일 모드).
  • derived(자동 노출) handle 수용 (전체 위임·서버 동작, SDK 표면 무변경): 명시 노출 없이 deviceHandles 에 자동 노출 handle(atg_)을 그대로 넣어 생성할 수 있다(broadcast.* 와 동일하게 전 경로 관통). 생성은 all-or-none — 하나라도 현재 실효 노출 밖이면 404 TARGET_UNRESOLVED(부분 생성 없음). 조회(get/list)의 deviceHandles조회 시점의 실효 노출 기준으로 재도출된다(저장 시점 고정 아님): explicit(tgt_) 우선 · 자동 노출은 atg_ · 노출이 사라진(미할당·전환) 디바이스는 제외(존재 은닉, §3.7 이력과 동형). 예약 발화 시에는 발화 시점 실효 노출분만 대상이 되고 나머지는 조용히 제외된다(부분 발화) — 즉 create=all-or-none 이지만 fire=partial-drop 인 의도된 비대칭.
  • list() · get(handle) · update(handle, UpdateTemplateInput) · delete(handle) — delete 는 참조 예약 존재 시 409.
  • 알려진 계약 비대칭: CreateTemplateInput.languageOrderstring(CSV, 예 "ko,en") 인 반면 FromContentInput.languageOrderstring[] 다. 역사적 사실로 유지되는 계약이며, 통일 시도는 breaking 판정 대상이다.

3.6 schedules — 예약 방송 CRUD

  • create(CreateScheduleInput) · list({ search? }?) · get(handle) · update(handle, UpdateScheduleInput) · delete(handle)
  • search 는 예약 이름 부분일치(대소문자무시), 미지정 시 전량. 필터 파라미터는 0.6.0+ — 이 필터를 포함한 api-server 릴리스 이후에만 서버가 적용한다(구서버는 파라미터를 무시하고 전량 반환).
  • UpdateScheduleInputPartial 기반으로 타입이 서버 검증보다 느슨하다 — 최종 authority 는 서버(400 응답). (알려진 DX 갭 — 후속 강화 후보)

3.7 history — 방송 이력 읽기 전용

  • list({ page?, size?, type?, search? }?) — 페이지네이션 + 필터, 반환 Page<HistoryView>. 정렬은 서버 고정(createdAt,id DESC) — client sort 지정 불가, 클라이언트 재정렬은 소비자 책임.
    • type?: 'MIC' | 'TTS' — 공개 계약값. 서버는 wire type 을 이 값으로 양방향 매핑한다(요청: 'TTS'→내부 AUDIO·'MIC'MIC, 미지정=전체 / 응답: 아래 HistoryView.type 참조).
    • search?: string — 컨텐츠 제목 부분일치(대소문자무시), 미지정 시 전량.
    • 필터 파라미터(type·search)는 0.6.0+ — 이 필터를 포함한 api-server 릴리스 이후에만 서버가 적용한다(구서버는 파라미터를 무시하고 전량 반환, 정렬도 서버 기본값을 따른다).
  • get(sessionId) — 상세. deviceHandles(당시 대상)는 상세에서만 채워진다.
  • deviceHandles 는 조회 시점의 실효 노출 기준이다. 운영 방식 전환(전체 위임→선별)으로 노출이 사라진 디바이스는 목록에서 제외되며, 전부 사라진 경우 빈 배열이 정상이다.
  • 응답 HistoryView.type'MIC' | 'TTS' 0.6.0+(narrowing — 이전은 string). SDK 가 list()/get() 응답을 단일 지점에서 정규화한다(wire AUDIO→공개 TTS). 서버가 알 수 없는 wire type 값을 내려주면 조용히 오분류하지 않고 ProtocolError(§5.2)를 throw 한다.

3.8 languages 0.2.0+ — 현장 활성 언어 읽기 전용

  • list() — 앱이 바인딩된 현장의 활성 TTS 언어. 언어 선택 UI 의 소스.
  • 정렬: ko(원문 언어) 우선, 이후 name 가나다 오름차순. name 은 한국어 표기 — 서버 카탈로그 V068 이후 기준(이전 서버는 자체 표기 native + 코드순 정렬). 값 시맨틱은 서버 릴리스 소관, SDK 타입 표면 무변경.

3.9 broadcast.tts

  • send(TtsSendInput) — 직접 텍스트 TTS 방송. 대상은 targetHandle 또는 deviceHandles[] 중 하나 (서버 XOR 400, deviceHandles 모드 0.4.0+). Idempotency-Key 헤더로 input.idempotencyKey 전송. 반환 BroadcastReceipt판정은 status (admitted? 는 파생 필드).
  • stop(sessionId) 0.2.0+ — 자기 소유 TTS 방송 중단(재생 중 세션만). 종결 관찰은 SSE phase=STOPPED.
  • fromContent(FromContentInput) 0.2.0+ — 노출/자작 컨텐츠 즉시 방송. 대상은 targetHandle 또는 deviceHandles[] 중 하나 (서버 XOR 400).
  • 방송은 보낸 handle 집합 그대로 대상이 되며, 입장 시점에 사용 가능 여부가 재검사된다. 예약 방송은 생성 시점 device set 이 발화 시점에 노출 재검사된다 — 전체 노출 해제된 예약 발화는 파트너 이력에 남지 않는다.

3.10 broadcast.subscribe(sessionId, handlers) → { close() }

SSE 기반 진행 구독 (스냅샷 우선 emit → 스트림).

  • SubscribeHandlers: onPhase(PhaseSnapshot) 필수 · onError?(OpenEchoError) · onClose?() (1회 보장)
  • 내부 재연결(backoff 최대 30s)·중복 제거(rank + per-device 내용 기준)·terminal 시 자동 종료. close() 는 멱등.
  • 에러는 throw 되지 않고 onError 로 전달된다. 426(SdkVersionUnsupported)은 재연결 없이 즉시 종결 (0.3.0+).

3.11 broadcast.mic

  • isSupported(): boolean — WebRTC(RTCPeerConnection) 존재 확인.
  • preload(): Promise<void> — MIC 청크 선로드 (single-flight, 중복 호출 안전, 실패 시 재시도 가능).
  • start(MicStartInput): Promise<MicSession>마이크 선확보(0.5.0+) → MIC lease 획득 → 실시간 오디오 룸 연결·발행. 대상은 targetHandle 또는 deviceHandles[] 중 하나 (서버 XOR 400, deviceHandles 모드 0.4.0+). 마이크 획득 실패 시 MicUnavailable(서버 미접촉 — 세션·점유 없음, 0.5.0+). 연결·발행 실패 시 SDK 가 자원 정리 + 보상 stop 을 수행한 뒤 원 에러를 던진다(0.5.0+). npm 경로는 이 시점에 청크 동적 로드(실패 시 MicChunkLoadFailed). replay 주의: 로컬 마이크 선확보가 서버 idempotency replay 에 선행 — 동일 Idempotency-Key 재시도라도 마이크 실패 시 서버 결과를 재현하지 않고 MicUnavailable 을 반환한다(서버 상태 불변).
  • MicSession: sessionId · resolvedDeviceCount: number(lease 시점 대상 디바이스 수 — BroadcastReceipt.resolvedDeviceCount 동형, 0.7.0+) · stop(): Promise<void> (멱등).
  • (api-server 0.38.0) 부분 활성화: 대상 디바이스 일부가 조인에 실패해도 1대 이상 조인하면 방송이 시작된다(조인 실패 디바이스는 제외). 전 디바이스가 실패하면 세션이 MIC_JOIN_FAILED 로 종착한다. 진행 중 이력(history)의 fail 집계에 조인 실패가 반영된다. SDK 코드 변경 불요 — 서버측 동작 변경(코드 상세는 §5.4 "파트너 MIC 세션 전용" 참조).
  • (api-server 0.38.0) heartbeat 허용 확장: 내부 자동 heartbeat(15초 간격, stop() 전까지 계속)가 세션이 아직 조인 대기 중(CREATING)이어도 200 으로 수락된다(TTL·grant 연장 전용 — 상태 전이 없음). 종결된 세션(STOPPED/PARTIAL 등)에 대한 heartbeat 는 여전히 거부되며 err.code 로 종착 사유를 구분할 수 있다 (MIC_SESSION_ENDED/MIC_JOIN_FAILED/MIC_SESSION_EXPIRED). heartbeat 는 SDK 내부 자동 호출이라 파트너 코드가 직접 다룰 필요는 없다.
  • 종착 자동정리 + onSessionEnded (0.7.0+): 내부 heartbeat 가 서버 세션 종착 4종 (MIC_SESSION_NOT_FOUND/MIC_SESSION_ENDED/MIC_JOIN_FAILED/MIC_SESSION_EXPIRED, §5.4) 또는 연속 인증 실패(401/403 이 연속 4회 도달)를 감지하면, 콜백 지정 여부와 무관하게 마이크 트랙·실시간 오디오 룸을 즉시 로컬 정리한다(서버는 이미 종착 상태이므로 stop() 재호출은 생략). 정리 후 이 stop() 은 멱등 no-op 이 된다. 정리 직후 onSessionEnded?.(reason)최대 1회 호출한다 — reason 은 위 4종 서버 code ∪ "AUTH_FAILED"(auth-lost 스트릭)이며 값 집합은 확장될 수 있으므로 알 수 없는 값도 종료로 처리할 것. 콜백이 던지는 예외는 SDK 밖으로 전파되지 않는다. 로컬 stop() 호출 경로에서는 미발화. 0.6.x 대비 행동 변경 — 이전에는 heartbeat 실패가 완전히 침묵했다(마이크가 죽은 세션에 계속 열려 있었다). CHANGELOG 0.7.0 참조.

4. 공개 타입

"서버 확장" = 구버전 서버에서 undefined 가능 (§0 표기 규약). 이 규약은 §4.1 뷰 타입뿐 아니라 §4.3 방송 진행 타입(예: PhaseSnapshot.devices)을 포함한 서버 응답을 담는 모든 공개 타입에 적용된다.

4.1 뷰 타입 (서버 응답)

DeviceViewhandle · name · status · online?: boolean 0.2.0+(서버 확장 — 정보 라벨, 방송 시점 상태 비보장)

GroupViewhandle · label · memberCount · status? 0.2.0+ · description?: string | null 0.2.0+ (서버 확장)

ContentViewhandle · title · scopeType · 0.2.0+(서버 확장): languages?: string[](재생순 canonical) · body?: string | null(자작만, 노출분 null) · durationSeconds?: number | null(합성 전 null)

TemplateViewhandle · name · contentHandle · targetMode · 0.2.0+(서버 확장): languageOrder? · groupHandle? · deviceHandles?: string[] | null

ScheduleViewhandle · name · templateHandle · recurrence · executeTime · executionAvailability · 0.2.0+(서버 확장, 수정 폼 프리필용): executeDate? · daysOfWeek? · monthlyDay? · isLastDay? · startDate? · endDate? · notes?

HistoryViewsessionId · type: 'MIC' | 'TTS'(0.6.0+ narrowing — 0.5.0 까지는 string. SDK 가 wire AUDIOTTS 로 정규화하며 unknown wire 값은 ProtocolError, §3.7·§5.2) · status · totalDevices · successDevices · failDevices · createdAt · triggeredBy · 0.2.0+(서버 확장): contentTitle? · contentHandle?(접근 가능 컨텐츠만, 재방송 조합용) · deviceHandles?(상세만)

Page<T>content: T[] · page · size · totalElements

LanguageOptioncode(canonical lowercase) · name(표시명 — 한국어 표기, §3.8). 타입 export 는 0.2.2+ (0.2.0~0.2.1 은 export 누락 결함).

PingResultappName · appId · origin

4.2 입력 타입

TtsSendInputtargetHandle? | deviceHandles?: string[](XOR — deviceHandles 모드 0.4.0+) · text · language · idempotencyKey(필수) · additionalLanguages?: string[] 0.2.0+(다국어, 재생 순서 유지) · partial?: boolean · expectedRevision?: number

FromContentInput 0.2.0+contentHandle · targetHandle? | deviceHandles?: string[](XOR) · languageOrder?: string[](컨텐츠 언어와 어긋나면 서버 lenient 병합) · partial? · expectedRevision? · idempotencyKey(필수)

MicStartInputtargetHandle? | deviceHandles?: string[](XOR — deviceHandles 모드 0.4.0+) · partial? · idempotencyKey(필수) · onSessionEnded?: (reason: string) => void(0.7.0+ — 서버 세션 종착 또는 인증 상실을 heartbeat 로 감지했을 때 최대 1회 호출. reason = 서버 code ∪ "AUTH_FAILED" — 값 집합 확장 가능, 알 수 없는 값도 종료로 처리할 것. 로컬 stop() 시에는 미발화. 상세는 §3.11)

CreateContentInput 0.2.0+title · text · languages: string[] UpdateContentInput 0.2.0+title · addLanguages?: string[]

CreateTemplateInputname · contentHandle · groupHandle? | deviceHandles?: string[](XOR — deviceHandles 모드 0.2.0+) · languageOrder?: string UpdateTemplateInputname · languageOrder?: string

CreateScheduleInputtemplateHandle · name · recurrence: "ONCE"|"DAILY"|"WEEKLY"|"MONTHLY" · executeTime · executeDate? · daysOfWeek? · monthlyDay? · isLastDay: boolean · startDate · endDate? · notes? UpdateScheduleInputPartial<Omit<CreateScheduleInput, "isLastDay">> & { isLastDay?: boolean }

4.3 방송 진행 타입

BroadcastReceiptstatus: "ADMITTED" | "DENIED_BUSINESS"(canonical 판정 신호) · sessionId: string | null · resolvedDeviceCount · revision · denyReason: string | null · skipped: string[] · admitted?: boolean 0.2.2+(서버 직렬화 파생 필드 = status === "ADMITTED" — 판정에 쓰지 말 것)

PhaseSnapshotsessionId · phase · terminal: boolean · reason: string | null · lastEventId · receipt: BroadcastReceipt | null · devices?: DeviceProgress[] 0.2.0+(서버 확장) · skippedAtDispatch?: number 0.7.0+(서버 확장 — 송출 시점 skip 된 대상 수, 구서버는 undefined)

DeviceProgress 0.2.0+deviceName · status(open string, 미지 값은 무시할 것). 어휘는 소스별로 다르다:

  • TTS: PENDING|PLAYING|DONE|FAILED
  • MIC(0.7.0+): PENDING|JOINED|FAILED|TIMEOUTSKIPPED 는 이 배열에서 제외되며 아래 skippedAtDispatch 가 별도 집계한다

MIC 의 devices[] 는 "조인 결과"다 — 세션이 종결(terminal)된 이후에도 각 행은 마지막 join_state 를 유지한다. TTS 처럼 실시간 재생 상태를 반영하는 것이 아니라 "누가 조인했는가"의 결과 스냅샷이다.

MIC devices[] 빈 배열 판별: PhaseSnapshot.skippedAtDispatch?: number(0.7.0+, 서버 확장 — §0 규약, 구서버는 undefined)로 원인을 구분한다. MIC 세션의 devices 가 빈 배열로 도착하면: skippedAtDispatch > 0 이면 전 대상이 송출 시점에 skip 되어 정상적으로 빈 것이고, skippedAtDispatch0 이거나 undefineddevices[] 투영을 지원하지 않는 구서버(api-server 0.39.0 미만 — MIC 는 항상 빈 배열 반환)의 응답일 가능성을 의심한다.

StreamTicketticket · expiresInSeconds (subscribe 내부에서 소비 — 직접 사용 경로는 비공개 표면)

SubscribeHandlers — §3.10

MicSession — §3.11

4.4 클라이언트 타입

OpenEchoClientcreateClient 반환 타입. _ 접두 멤버 제외는 0.2.2+ (그 전 타입에는 _http 가 보였으나 계약이 아니었음).

ClientConfig — §1.3


5. 에러 계약

모든 에러는 OpenEchoError 추상 클래스를 상속하며 code(wire 코드)·httpStatus·message·name(클래스명)을 보존한다. instanceof 판별을 지원한다.

§5.1 의 표는 "매핑된 16종의 무결성(코드↔클래스 1:1)"을 보장할 뿐, 서버가 방출 가능한 코드의 전수가 아니다. 미매핑 코드는 UnknownOpenEchoError 로 도착하며 code 를 보존한다 (§5.3·§5.4).

5.1 매핑된 wire 에러 (코드 ↔ 클래스 bijection — 비전수)

클래스wire codeHTTP의미
UnauthorizedPARTNER_UNAUTHORIZED401publicKey 무효/거부
TicketUnauthorizedPARTNER_TICKET_UNAUTHORIZED401SSE 스트림 티켓 인증 실패
OriginForbiddenPARTNER_ORIGIN_FORBIDDEN403등록 도메인 밖 호출 (도메인 잠금)
CapabilityDeniedCAPABILITY_DENIED403기능 capability 미허가
TargetUnresolvedTARGET_UNRESOLVED404대상 handle 해석 불가
ContentNotReadyBROADCAST_CONTENT_NOT_READY400컨텐츠 오디오 미준비 — MIC 경로에서는 api-server 0.38.0+ 부터 더 이상 발생하지 않는다(조인 부분 활성화 도입으로 대상 판정이 이동, TTS 경로는 무변화)
BroadcastIneligibleBROADCAST_INELIGIBLE409방송 불가 상태 (동시 방송 등)
IdempotencyKeyReusedIDEMPOTENCY_KEY_REUSED409동일 키가 다른 페이로드로 재사용
TargetRevisionStaleTARGET_REVISION_STALE409대상 revision 변경 — expectedRevision 갱신 필요
SessionExpiredSTREAM_GONE410SSE 세션 만료
UsageCapExceededUSAGE_CAP_EXCEEDED429일일 TTS quota 초과
RateLimitedRATE_LIMITED429요청 속도 제한 초과
StreamLimitExceededSTREAM_LIMIT_EXCEEDED429SSE 동시 스트림 한도 초과
RateLimitBackendUnavailableRATE_LIMIT_BACKEND_UNAVAILABLE503속도 제한 백엔드 일시 불가
SdkVersionUnsupported 0.3.0+SDK_VERSION_UNSUPPORTED426서버 최소 SDK 버전 미만 — 재시도 무의미, SDK 업그레이드가 유일한 해소
DeviceInUse 0.4.0+DEVICE_IN_USE409대상 중 사용 중 디바이스 포함 — 방송 전체 거절 (§5.4 승격 규칙의 첫 적용)

5.2 클라이언트 합성 에러 (wire 코드 없음)

클래스발생 지점의미
MicChunkLoadFailedmic.start()/preload()MIC 청크 로드 실패 (네트워크/CSP)
MicUnavailablemic.start()마이크 획득 실패 (0.5.0+, 클라이언트측 — wire 아님). reason: "permission-denied" | "not-found" | "other" · causeName · causeMessage 필드
GatewayMasked전 요청게이트웨이가 에러를 HTML 200 등으로 마스킹 — API 응답 아님
InvalidApiResponse전 요청응답이 API 봉투 형식이 아님
ProtocolError 0.6.0+history.list()/get() 응답 정규화서버가 알 수 없는 HistoryView.type wire 값을 내려줌 — 조용한 오분류 대신 즉시 실패 (httpStatus 502, 클라이언트측 — wire 응답 그 자체는 200이었을 수 있음)

5.3 forward-compatibility

  • UnknownOpenEchoError — bijection 에 없는 wire 코드는 이 클래스로 도착한다. 서버가 새 에러 코드를 추가해도 SDK 는 깨지지 않는다. 소비자는 최종 fallback 으로 instanceof OpenEchoError 를 두어야 한다.
  • 새 wire 코드의 전용 클래스 추가는 additive(minor) — 단, 그 순간부터 해당 코드는 UnknownOpenEchoError 가 아닌 전용 클래스로 도착하므로 CHANGELOG 에 명시한다.

5.4 미매핑 wire 코드 (비전수 채널)

서버 에러 채널 구조상 코드 단위 전수 목록은 원리적으로 유지 불가능하다 — BE GlobalExceptionHandlerBusinessException catch-all 이 모든 도메인 ErrorCode 를 통과시키므로, 코드 단위 목록은 작성 즉시 드리프트가 시작된다. 따라서 이 절은 채널 단위로 기술한다:

방출 채널대표 코드 (예시, 비전수)도착 형태
Spring generic 핸들러 (요청 형식 오류)VALIDATION_FAILED(400) · INVALID_REQUEST_BODY(400) · MISSING_HEADERUnknownOpenEchoError (code 보존)
BusinessException catch-all (도메인 ErrorCode 전반)예: DEVICE_ALREADY_REGISTERED(409)UnknownOpenEchoError (code 보존) — 승격된 코드(DEVICE_IN_USE 등, §5.1)는 제외
파트너 MIC 세션 전용 (api-server 0.38.0, BusinessException 하위집합 — 아래 상세)MIC_SESSION_NOT_FOUND 외 5종UnknownOpenEchoError (code 보존)

파트너 MIC 세션 코드 상세 (api-server 0.38.0 — 조인 부분 활성화 도입에 따른 세분화, §3.11 참조):

wire codeHTTP의미
MIC_SESSION_NOT_FOUND404MIC 세션 부재·비소유
MIC_NO_ELIGIBLE_DEVICE422시도 가능 디바이스 없음 — 세션 미생성
MIC_SESSION_ENDED409종료된 MIC 세션 호출 — 파트너 stop()·heartbeat 만료·기타 서버 종료의 union이며 원인을 구분하지 않는다(구분이 필요하면 Phase 3 terminalReason 표면화로 위임)
MIC_JOIN_FAILED409전 디바이스 조인 실패로 종착된 세션
MIC_SESSION_EXPIRED409최대 시간 초과 종착
MIC_LEASE_HELD409lease 발급 불가 상태

위 6종은 전용 클래스가 없다 — 전부 UnknownOpenEchoError 로 도착(code 보존). 프로그램적 분기가 필요하면 err.code 문자열 비교(승격 전 상태 — 아래 승격 규칙 참조).

위 4종(MIC_SESSION_NOT_FOUND/MIC_SESSION_ENDED/MIC_JOIN_FAILED/MIC_SESSION_EXPIRED)은 SDK 0.7.0+mic.start() 내부 heartbeat 가 종착 코드로 구조적 판별(err.code 문자열 비교, instanceof 금지 — 청크·core 별도 번들 그래프)해 자동 로컬 정리 + onSessionEnded 트리거를 수행한다(§3.11). MIC_LEASE_HELD 는 비종착 취급(무시, 다음 heartbeat 재시도) — 위 4종과 혼동하지 말 것.

승격 규칙: 미매핑 코드가 파트너 코드의 프로그램적 분기 대상이 되고 구조화 payload 가 확정되면, 전용 공개 클래스로 승격한다 = additive(minor, §5.3). 승격 전까지 판별 경로는 err.code 문자열 비교다.

DEVICE_IN_USE + busyHandles payload 는 partner-multi-target 트랙에서 0.4.0 에서 적용됨 — §5.1 DeviceInUse 참조.

5.5 에러 객체 계약 (현재 규범)

OpenEchoError 의 계약 필드는 code · httpStatus · message (+ name = 클래스명) · details?: Record<string, unknown> 0.4.0+(서버 error body 의 code/message 외 필드가 보존된다 — 결손 시 undefined) — 이 필드들이 계약의 전부다.

5.5.1 비규범 예약 — 구조화 payload (0.4.0 에서 §5.5 로 승격됨)

이 절은 details? 도입 전 예고문을 담고 있었다. 0.4.0 에서 §5.5 규범으로 승격되어 이 절은 더 이상 별도 내용을 갖지 않는다.

5.6 보조 export (고급)

mapError(httpStatus, code, message, details?): OpenEchoErrorERROR_BIJECTION(코드→클래스 레코드)은 공개 export 다. 매핑 조회 용도로만 안정 계약이며, 레코드를 변형하는 사용은 지원하지 않는다. details?: Record<string, unknown>0.4.0+.

5.7 전달 경로

REST 메서드 = throw · SSE(subscribe) = onError 콜백 · createClient 의 설정 검증 = 일반 Error throw (§1.3 — wire 이전 단계이므로 OpenEchoError 아님).