Chat JS SDK
Chat JS SDK는 방과 메시지, typing/read, WebSocket 연결을 앱 코드에서 소비하기 쉽게 만든다.
2026-09-06 조회한 공개 문서는 0.1.0을 표시한다. 역할 70이 확인한 registry 버전은 0.1.3이며
이 발행본에는 나가기·membership API가 없다. 새 SDK 소스는 0.2.0 준비 버전이다. 로컬 SDK와 새 나가기 기능의
상태를 공개 배포 상태와 혼동하지 않는다. 실제 탈퇴·타임아웃 확인·재개/재초대 지원 경계는
Chat 대화방 나가기를 따른다. 새 나가기 기능은 unpublished / undeployed다.
PostgreSQL 로컬 계약
2026-09-06 신규 PG 경로의 실행 계약이다. 상태는 local-only / unpublished / undeployed이며
기존 운영 Map 서버나 registry 0.1.3의 보장이 아니다. 운영 데이터 보존·이관은 **BLOCKED**다.
SDK 0.2.0의 공개 API와 버전은 이 계약 때문에 변경하지 않는다.
최종 local P4는 Chat 8540121·Auth 97e2419 고정 빌드와 packed Chat SDK bd9b877(0.2.0),
packed Auth SDK 94fb11d(0.1.10)를 연결해 9 subtests+parent+guard2, 총12 pass/0skip를 확인했다.
실제 Auth HTTP 발급·session refresh·PG introspection·JWKS 서명과 Chat HTTP/WS·PG를 사용했다.
초기 합성 identity/session과 old/read-only token 준비만 별도 test-control이며, 외부 Google/Apple/Keycloak
로그인은 실행하지 않았다. Delivery는 fake이고 외부 Provider 요청은 차단했다.
실제 Auth SDK의 공개 sessionStore에 합성 초기 session을 주입한 이후 grant/refresh는 실제 HTTP다.
old/read-only leave 거절·1회 refresh 제한, Secret Key self-leave 거절, timeout 뒤 membership 확인,
Auth identity 교체와 늦은 응답, 타방 연결 유지, fresh ticket/reconnect를 검증했다.
API child PID 교체1회 뒤 history/read/최초 leftAt/dedup과 old ticket 거절도 확인했다.
SDK 검증은 DB container 재시작·backup·binary rollback을 직접 수행한 증거가 아니다.
선행 Chat 192f583의 fixture 기반 PG 결과와 baseline P4는 이 최종 실행에 합산하지 않는다.
- 전송 재시도는 같은 room·sender·환경에서 같은
clientMessageId와 같은 payload를 유지한다.
서버는 이미 저장된 message ID/sequence를 반환한다. 같은 키의 다른 payload는
409 CHAT_MESSAGE_IDEMPOTENCY_CONFLICT이며 새 키로 자동 재시도해 충돌을 숨기지 않는다.
탈퇴한 sender는 이전 전송 결과를 재조회하는 방식으로 권한을 우회하지 못한다.
- 비교할 JSON은 object key 순서를 정규화하지만 array 순서와 content의 unknown JSON 필드는 유지한다.
reply 미지정은 null, mentions 미지정은 빈 배열로 비교한다. WS envelope의 sequence는
프로세스 로컬 event 번호이며 durable message sequence나 재시작 후 replay cursor가 아니다.
- 서버 read 입력은 safe integer이며 0 또는 해당 room에 존재하는 양수 message sequence다.
현재 committed 마지막 sequence 초과·음수·소수·unsafe 값은 400 CHAT_BAD_REQUEST다.
과거값 입력은 저장된 읽음 위치를 낮추지 않으며 HTTP data.last_read_server_sequence와
read 이벤트에는 max(old, input)을 반환한다.
- SDK
markRead(roomId, { lastReadSequence })는 기존대로 **양의 정수 입력·Promise<void>**다.
HTTP의 0 입력과 저장값 반환을 이 SDK 메서드가 직접 제공한다고 가정하지 않는다.
최신 저장 상태는 listRooms()의 lastReadSequence로 확인한다.
- 서버의 tenant/environment는 Auth가 검증한 principal에서 가져온다. 앱이 임의 header/body로 환경을
바꾸거나 과거 방을 현재 환경에 귀속시키지 않는다. Auth의 사용자 ID만으로 과거 방의 환경을 확정할 수 없다.
현 Auth는 같은 project/service에 하나의 environment만 활성화한다. P4는 다른 Test/Live 프로젝트 격리와
같은 프로젝트의 서비스 환경 변경 시 old token 거절·원복을 구분해 검증했다. Chat DB의 환경별 저장
격리는 Auth의 같은 프로젝트 Test/Live 동시 활성화 지원을 뜻하지 않는다.
503 CHAT_STORAGE_UNAVAILABLE은 DB 연결·저장 결과 불명,503 CHAT_STORAGE_NOT_READY는
schema·이관 상태·단일 authority 준비 실패다. 어느 쪽도 빈 방·탈퇴 완료의 증거가 아니다.
서버는 메모리 저장소로 전환하지 않는다. 응답 불명 시 send는 같은 키·내용을 유지하고,
leave는 본인 membership 확인으로 결과를 확인한다.
합성 데이터의 local PostgreSQL 복원, 선행 Auth fixture 통합, 실제 local Auth P4,
외부 Provider 로그인과 기존 운영 데이터 추출·복원·전환은 별도 검증이다. 독립 multi-instance, exactly-once 알림,
offline WS replay, 이미 전달된 자료·Storage URL 회수는 보장하지 않는다.
장애와 복구 범위
Chat 8540121은 Auth introspection의 header/body 전체에 5초 제한을 적용하고 기존
503 CHAT_AUTH_UNAVAILABLE을 반환한다. 서버가 자동 재시도하지 않으며 SDK 요청 deadline과는 별도다.
DB timeout·deadlock·연결 단절과 저장 공간 부족의 실패·복구를 격리 합성 환경에서 검증했다.
실패 요청의 성공 ACK는 허용하지 않고 commit 불명 시 동일 키·내용 또는 membership 확인으로 복구한다.
PostgreSQL 53100 또는 ROLLBACK 불명을 관측하면 authority와 readiness를 fail closed로 유지한다.
단순 읽기 성공이나 용량 복구만으로 자동 재개하지 않으며, 서버 담당의 원인 해소·명시적 API 재기동과
readiness 확인 뒤 같은 clientMessageId·payload로 결과를 확인한다. 확정 ROLLBACK인 timeout/deadlock은
실패 요청의 503과 이후 readiness 200이 공존할 수 있으며 이는 쓰기 성공 증거가 아니다.
실제 공간 부족 시험은 작은 tmpfs scratch에 쓰는 test trigger에서 발생한 53100을 Chat transaction에
전달한 것이다. PGDATA/WAL/호스트 전체 디스크 고갈, 운영 규모의 장애 조합을 검증한 것은 아니다.
서버의 별도 local 시험은 192f583 → 8540121 → 192f583 → 8540121의 서로 다른 영속 바이너리에서
최신 메시지·읽음·최초 탈퇴 시각·dedup과 추가 쓰기 보존을 확인했다. 같은 binary 재시작이나
container image rollback, 운영 무손실 이관 증거로 바꾸어 설명하지 않는다.
이 호환성 시험은 candidate가 수정한 Auth·용량 장애 도중 baseline으로 복귀해도 안전하다는 보장이 아니다.
별도 고정 스크립트의 후속1회 시험은 outbox의 불변 이벤트 필드·message/member 참조·중복 행 없음과
미연결 Delivery의 pending 상태를 확인했다. 바뀔 수 있는 재시도·lease 상태, 실제 발송과 exactly-once는
보장하지 않으며 운영 이관 승인을 대체하지 않는다.
설치
npm install @spectra-platform/chat-sdk
API tree
Chat JS SDK
├─ Client setup
│ ├─ new SpectraChatClient(config)
│ ├─ createSpectraChatClient(config)
│ └─ on(event, handler)
├─ Rooms
│ ├─ listRooms(options?)
│ ├─ createDirectRoom({ userId })
│ ├─ createGroupRoom({ title?, userIds })
│ ├─ leaveRoom(roomId, options?) # 0.2.0 unpublished
│ └─ getRoomMembership(roomId, options?) # 0.2.0 unpublished
├─ Messages
│ ├─ listMessages(roomId, options?)
│ ├─ sendMessage(roomId, options)
│ ├─ uploadFiles(roomId, input)
│ ├─ sendMessageWithFiles(roomId, input)
│ └─ markRead(roomId, { lastReadSequence })
└─ Realtime
├─ connect(roomId)
├─ setTyping(roomId, isTyping)
└─ disconnect()
Auth 객체와 함께 사용
Chat SDK는 Auth SDK의 access token provider를 주입받는다. Chat SDK가 필요할 때 auth.getAccessToken({ service: "chat" })을 호출하므로 앱에는 Chat service secret이 들어가지 않는다.
import { SpectraAuthClient } from "@spectra-platform/auth-sdk";
import { SpectraChatClient } from "@spectra-platform/chat-sdk";
const auth = new SpectraAuthClient({
projectId: "proj_...",
// Current Auth SDK compatibility field. It is not a primary product key.
publicClientId: "app_...",
environment: "test",
redirectUri: "https://myapp.example.com/auth/callback",
});
const chat = new SpectraChatClient({
projectId: "proj_...",
auth,
});
API detail
new SpectraChatClient(config)
Chat client를 만든다.
projectId: Console Project IDauth:getAccessToken({ service: "chat" })을 제공하는 Auth 객체baseUrl: 선택 사항. 기본값은https://chat.spectra.krwebSocket: 선택 사항. 브라우저 또는 테스트 환경의 WebSocket factoryreconnectDelayMs: 예상치 못한 close 후 재연결 지연 시간
createSpectraChatClient(config)
new SpectraChatClient(config)와 같은 결과를 반환하는 factory helper다.
on(event, handler)
Realtime 이벤트를 구독한다. 반환값은 unsubscribe 함수다.
const offMessage = chat.on("message", (event) => {
console.log(event.roomId, event.message.text);
});
chat.on("typing", (event) => {
console.log(event.userId, event.isTyping);
});
지원 이벤트:
message: 새 메시지typing: typing 상태 변경read: read cursor 변경connected: WebSocket 연결 준비disconnected: WebSocket 연결 종료error: SDK 또는 socket 오류membership: 본인의 방 탈퇴 상태 변경.0.2.0unpublished. 중복 가능하며 다른 방 연결을 유지한다.
listRooms(options?)
현재 app-user가 참여한 방 목록을 조회한다.
const rooms = await chat.listRooms({ limit: 30 });
createDirectRoom({ userId })
상대 app-user와 새로운 1:1 방을 생성한다. 현재 서버는 같은 상대의 기존 방을 찾아 반환하거나
탈퇴한 방을 되살리지 않는다. 소비 앱의 ensureDM에서 나가기 복구 수단으로 자동 호출하지 않는다.
const room = await chat.createDirectRoom({
userId: "app_user_other",
});
createGroupRoom({ title?, userIds })
여러 app-user가 참여하는 그룹방을 생성한다.
const room = await chat.createGroupRoom({
title: "Modo Camp Study",
userIds: ["app_user_a", "app_user_b"],
});
listMessages(roomId, options?)
탈퇴한 방의 과거 기록은 이 API로 조회할 수 없다. 남은 참여자의 조회 권한과 데이터 보존은 별개다.
방 메시지를 조회한다.
const messages = await chat.listMessages(room.id, {
beforeSequence: 120,
limit: 50,
});
sendMessage(roomId, options)
text 또는 Storage object reference attachment를 메시지로 전송한다. 파일 전송의 기본 UX는 아래 sendMessageWithFiles() 방향으로 정렬한다.
await chat.sendMessage(room.id, {
text: "안녕!",
});
await chat.sendMessage(room.id, {
text: "사진 첨부",
attachments: [
{
objectKey: "/chat/room-1/photo.png",
contentType: "image/png",
},
],
});
markRead(roomId, { lastReadSequence })
내 read cursor를 갱신한다. 반환값은 Promise<void>이며 입력은 양의 정수다.
신규 local PG 서버의 범위 검사와 monotonic 저장 정책은 위 PostgreSQL 로컬 계약을 따른다.
await chat.markRead(room.id, {
lastReadSequence: 121,
});
setTyping(roomId, isTyping)
WebSocket 연결 후 typing 상태를 보낸다.
chat.setTyping(room.id, true);
chat.setTyping(room.id, false);
connect(roomId)
방 realtime channel에 연결한다. SDK는 WebSocket ticket을 받고 socket을 열며, 예상치 못한 close 뒤에는 새 ticket으로 재연결한다.
await chat.connect(room.id);
disconnect()
재연결을 중단하고 현재 WebSocket을 닫는다. 본인의 방 membership은 유지되므로 실제 나가기가 아니다.
chat.disconnect();
leaveRoom(roomId, options?)
본인 membership을 해제하고 SpectraChatLeaveRoomResult를 반환한다. 단순 화면 닫기·목록 숨기기와 다르다.
0.2.0 소스 전용이며 발행본 0.1.3에는 없다. 마지막 참여자와 반복 요청도 같은 left 상태를 반환한다.
구체적인 사용 예제와 지원 한계는 나가기 계약을 따른다.
getRoomMembership(roomId, options?)
본인의 SpectraChatRoomMembership을 조회한다. left를 확인한 경우에만 결과가 불확실했던 나가기를
완료로 처리한다. active는 조회 시점의 상태이며 404는 탈퇴 증거가 아니다.
두 method의 options는 timeoutMs(기본 10,000ms)와 signal이다. 취소·타임아웃은 서버 rollback이 아니다.
Attachment
Chat은 binary를 직접 저장하지 않는다. 현재 SDK의 uploadFiles()와 sendMessageWithFiles()는
Storage 업로드와 object reference message 생성을 감싼다. 역할 70이 확인한 발행본 0.1.3에도 있는 기능이며
신규 나가기 0.2.0과 구분한다. 기본 업로드 visibility는 public_read다.
await chat.sendMessageWithFiles(room.id, {
text: "사진 첨부",
files: [
{
file,
},
],
upload: { visibility: "public_read" },
});
sendMessageWithFiles()의 visibility는 files[]가 아니라 upload.visibility에 넣는다.
private를 선택해도 Chat membership이 Storage 읽기 권한을 자동 부여하지 않는다. public_read는 URL을
가진 사람이 읽을 수 있고 Chat 탈퇴로 회수되지 않는다. 자세한 범위는 나가기와 기록 접근을 따른다.
아래처럼 Storage SDK로 업로드한 object reference를 직접 연결하는 방법도 있다. 이 조합은 advanced 사용법이다.
const image = await storage.uploadImage({
file,
directory: "/chat/room-1/",
visibility: "private",
context: "chat",
fileInfo: {
originalName: file.name,
fingerprint: `${file.name}|${file.size}|${file.type}|${file.lastModified}`,
lastModified: file.lastModified,
},
});
await chat.sendMessage(room.id, {
attachments: [
{
objectKey: image.objectKey,
contentType: image.contentType,
byteSize: image.byteSize,
checksumSha256: image.checksumSha256,
},
],
});
권한 모델
- Chat SDK는 app-user access token 또는 chat service token만 사용한다.
- 방 생성, 메시지 조회, 메시지 전송, read cursor, typing은 모두 현재 app-user 권한으로 동작한다.
- Secret Key는 환경(
test/live)에 묶인 만료일 없는 비밀값이며 최초 발급/재발급 직후 1회만 보여진다. 클라이언트에 둘지 여부는 소비 프로젝트의 명시적 아키텍처 결정이다. - 향후 서버 주도 room 관리나 moderation job이 필요하면 환경에 묶인
Project ID + Secret Key로 Project를 확인하고 Chat service toggle이 켜져 있을 때 수행한다. 기본 Chat SDK UX는 Project ID와 Auth 객체를 사용하며, 현재 package의publicClientId는 Auth SDK 호환 field로만 다룬다.
아직 별도 확인이 필요한 것
Chat SDK package는 배포됐지만 Chat Platform 서버 구현, WebSocket runtime, public domain E2E는 SDK 문서와 별도 gate로 확인해야 한다.