connectbase-client 6.6.0 → 6.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +95 -0
- package/README.md +46 -3
- package/dist/cli.js +1 -1
- package/dist/connect-base.umd.js +5 -5
- package/dist/index.d.mts +95 -15
- package/dist/index.d.ts +95 -15
- package/dist/index.js +145 -34
- package/dist/index.mjs +145 -34
- package/package.json +13 -7
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,101 @@
|
|
|
3
3
|
본 SDK 의 모든 주요 변경사항을 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/) 형식으로 기록합니다.
|
|
4
4
|
버전은 [Semantic Versioning](https://semver.org/lang/ko/) 을 따릅니다.
|
|
5
5
|
|
|
6
|
+
## [6.7.0] - 2026-10-22
|
|
7
|
+
|
|
8
|
+
### Added — 서버가 검증한 보낸 사람: `message.fromMember`
|
|
9
|
+
|
|
10
|
+
받은 메시지에 보낸 사람의 앱 멤버 ID 가 `fromMember` 로 실립니다(실시간 메시지와 히스토리 모두). 보낸 연결이 앱 멤버
|
|
11
|
+
`accessToken` 으로 접속했을 때만 있고, 익명(공개 키) 연결이 보낸 메시지에는 없습니다. 서버가 검증한 값이라 클라이언트가
|
|
12
|
+
위조할 수 없습니다(platform-issue 01a15e86).
|
|
13
|
+
|
|
14
|
+
지금까지 보낸 사람을 알 수 있는 값은 `from` 뿐이었는데, 이것은 연결이 접속할 때 스스로 정해 보내는 `client_id` 라 누구나
|
|
15
|
+
남의 값을 흉내 낼 수 있습니다. 누가 보냈는지 믿어야 하는 판단(권한, 점수, 신고, 차단)에는 `fromMember` 를 쓰세요.
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
chat.onMessage((m) => {
|
|
19
|
+
if (!m.fromMember) return // 로그인 멤버의 메시지만 받는 방
|
|
20
|
+
addChat(m.fromMember, m.data) // data 안에 넣은 userId 는 위조할 수 있다
|
|
21
|
+
})
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Added — room 구독: `subscribe(category, { room })`
|
|
25
|
+
|
|
26
|
+
카테고리 안의 room 하나를 구독합니다. 서버 채널 이름은 `category:room` 이고(문자열 `subscribe('concert:show-1025')` 와
|
|
27
|
+
같습니다) 구독 객체가 `channel` 과 `room` 을 드러냅니다. `send`, `getHistory`, `unsubscribe`, `onMessage` 와 재연결 뒤 다시
|
|
28
|
+
구독하기는 모두 이 채널 이름으로 동작합니다(platform-issue 01a15e81).
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
const stage = await cb.realtime.subscribe('concert', { room: 'show-1025' })
|
|
32
|
+
stage.channel // 'concert:show-1025'
|
|
33
|
+
stage.room // 'show-1025'
|
|
34
|
+
stage.info.isPublisher // room 인가 함수가 이 멤버를 방송인으로 정했으면 true
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
- room 은 영문, 숫자, `-`, `_`, `.` 로 1~128자입니다(서버 채널 규칙). 어긋나면 서버에 보내기 전에 에러를 던집니다 — 서버는
|
|
38
|
+
잘못된 채널을 `CATEGORY_NOT_FOUND` 로만 알려 원인이 보이지 않았습니다. 문자열 형태는 예전처럼 검증 없이 서버에 맡깁니다.
|
|
39
|
+
- 카테고리가 `require_room_auth` 이면 서버가 앱의 room 인가 함수에 구독을 묻습니다. 함수가
|
|
40
|
+
`{ "authorized": true, "publisher": true }` 를 돌려주면 그 로그인 멤버는 **그 room 의 방송인**이 됩니다(구독할 때 정해지고
|
|
41
|
+
`info.isPublisher`, 받는 쪽의 `fromPublisher` 에 반영). 카테고리 전체에 걸리는 역할(`publisher_roles`)과 달리 공연(room)마다
|
|
42
|
+
다른 방송인을 정할 수 있습니다.
|
|
43
|
+
- room 에 보내는(publish) 권한은 서버가 연결마다, room 마다 한 번만 함수에 묻고 기억합니다 — 메시지마다 함수를 부르지
|
|
44
|
+
않습니다.
|
|
45
|
+
- `realtime.unsubscribe()`, `realtime.markRead()`, `realtime.onReadReceipt()` 처럼 이름을 받는 메서드에는 `subscription.channel`
|
|
46
|
+
을 넘기세요.
|
|
47
|
+
|
|
48
|
+
### Added — 회원 역할 여러 개: `adminUpdateMember(id, { roles })`
|
|
49
|
+
|
|
50
|
+
`role`(주 역할, RLS `auth.role`) 외에 추가 역할 목록 `roles` 를 줄 수 있습니다. 보내면 목록을 통째로 바꾸고(`[]` 면 비움,
|
|
51
|
+
빼면 그대로) 응답에 `roles` 와 `effective_roles`(role + roles)가 실립니다. 실시간 카테고리의 `publisher_roles` 와 RLS
|
|
52
|
+
`auth.roles`, `hasRole()` 은 `effective_roles` 를 봅니다 — 운영자 계정에 `admin` 을 유지한 채 `performer` 를 더할 수 있습니다
|
|
53
|
+
(platform-issue 01a15e81). 콘솔 회원 상세와 MCP `update_member` 도 같은 필드를 받습니다.
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
await cb.auth.adminUpdateMember(memberId, { roles: ['performer'] })
|
|
57
|
+
// → { role: 'admin', roles: ['performer'], effective_roles: ['admin', 'performer'], ... }
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Server — 히스토리가 채널을 따릅니다 (2026-10-22 socket-server, SDK 판과 무관)
|
|
61
|
+
|
|
62
|
+
- room 채널의 `getHistory()` 는 그 room 에 저장된 메시지만, room 없는 채널은 room 없이 저장된 메시지만 돌려줍니다. 그 전에는
|
|
63
|
+
다른 room 의 저장 메시지가 섞였습니다.
|
|
64
|
+
- 그 전의 socket-server 는 core 의 히스토리 응답 형식을 잘못 읽어 `getHistory()` 가 늘 실패했습니다. 이번 배포로 동작합니다.
|
|
65
|
+
|
|
66
|
+
### Changed — `auth_unavailable` 도 `connect()` 안에서 스스로 다시 붙습니다
|
|
67
|
+
|
|
68
|
+
인증 서버가 잠깐 닿지 않아 서버가 `auth_unavailable` 로 거부하면 `connect()` 가 서버가 알려 준 시간(`retryAfterMs`)에 지터를
|
|
69
|
+
더해 다시 붙습니다 — `rate_limited` 와 같습니다. 재시도 시각을 싣지 않는 구버전 서버면 지수 백오프(0.25초부터, 최대 8초)로
|
|
70
|
+
다시 합니다. 연결 제한 시간(`timeout`, 기본 30초) 안에 못 붙을 때만 `RealtimeConnectionError`(`code: 'auth_unavailable'`)를
|
|
71
|
+
던집니다. 6.6.0 까지는 바로 던져, 인증 서버가 몇 초 흔들리는 동안 들어온 관객이 모두 연결에 실패했습니다(platform-issue
|
|
72
|
+
01a15e81). `unauthorized`, `quota_exceeded` 처럼 다시 해도 소용없는 거부와 `connection_limit_exceeded` 는 예전처럼 바로
|
|
73
|
+
던집니다.
|
|
74
|
+
|
|
75
|
+
### Fixed — 한 페이지의 여러 인스턴스가 동시에 연결하면 대부분 저지연 원본을 못 쓰던 문제
|
|
76
|
+
|
|
77
|
+
`new ConnectBase()` 10개가 동시에 `realtime.connect()` 하면 인스턴스마다 원본 힌트(`GET /v1/public/edge-hints`, 1.5초 한도)를
|
|
78
|
+
따로 물어, 같은 순간 몰린 요청 중 6~8개가 한도를 넘겨 원래 소켓 주소(Cloudflare 경유)로 붙었습니다. 이제 같은 `baseUrl` 의
|
|
79
|
+
인스턴스끼리 진행 중인 조회 하나를 함께 기다리고, 성공한 결과는 10분 동안 다시 씁니다(platform-issue 01a15e81). 조회 실패는
|
|
80
|
+
예전처럼 조용히 넘어가고 기억하지 않습니다. direct 원본 연결이 실패해 그 원본을 포기하는 판단은 인스턴스마다 따로입니다.
|
|
81
|
+
|
|
82
|
+
### Fixed — 플랫폼 이슈 보고의 `sdk_version` 이 늘 `"unknown"` 이던 문제
|
|
83
|
+
|
|
84
|
+
`cb.support.reportPlatformBug()` 가 자동으로 붙이는 `sdk_version` 을 빌드가 채우지 않아 늘 `"unknown"` 이었습니다. 이제 설치된
|
|
85
|
+
SDK 버전이 실립니다.
|
|
86
|
+
|
|
87
|
+
## [6.6.1] - 2026-10-21
|
|
88
|
+
|
|
89
|
+
### Fixed — 패키지 메타데이터(코드 변경 없음)
|
|
90
|
+
|
|
91
|
+
- **ESM 으로 import 하는 TypeScript 프로젝트가 CJS 타입을 받던 문제.** `exports` 가 import 와 require 모두에
|
|
92
|
+
`index.d.ts`(CJS 선언)를 줬다 — `moduleResolution: node16/nodenext` 에서 "Masquerading as CJS"(are-the-types-wrong).
|
|
93
|
+
이제 import 는 `index.d.mts` + `index.mjs`, require 는 `index.d.ts` + `index.js` 를 받는다. `bundler` 와 `node10`
|
|
94
|
+
해석은 그대로다.
|
|
95
|
+
- **`browser` 필드 제거.** 스크립트 태그용 UMD 번들을 가리켜, `exports` 를 모르는 옛 번들러(webpack 4 등)가 브라우저
|
|
96
|
+
빌드에서 `main` 대신 UMD 를 가져갔다. CDN 기본 파일은 `unpkg`, `jsdelivr` 필드로 지정한다(바뀌지 않는다).
|
|
97
|
+
- `exports` 에 `./package.json` 추가 — 도구가 `require('connectbase-client/package.json')` 로 판을 읽을 수 있다.
|
|
98
|
+
- `engines.node` 를 `>=16` 에서 `>=20` 으로 바로잡았다. SDK 는 전역 `fetch` 와 `crypto.randomUUID` 를 써서 Node 16,
|
|
99
|
+
18 에서는 원래 동작하지 않았다(Node 에서 실시간 WebSocket 은 전역 `WebSocket` 이 있는 22 이상).
|
|
100
|
+
|
|
6
101
|
## [6.6.0] - 2026-10-21
|
|
7
102
|
|
|
8
103
|
### Added — 카테고리 발행 정책: 방송인과 "위로만" 가는 메시지
|
package/README.md
CHANGED
|
@@ -612,10 +612,16 @@ await cb.auth.adminUpdateMember('019abc12-...', {
|
|
|
612
612
|
role: 'admin',
|
|
613
613
|
custom_data: { level: 5 },
|
|
614
614
|
})
|
|
615
|
+
|
|
616
|
+
// Additional roles (v6.7.0+): `roles` replaces the whole list ([] clears it, omit to keep it).
|
|
617
|
+
// Keep `role: 'admin'` and add 'performer' without losing admin.
|
|
618
|
+
const updated = await cb.auth.adminUpdateMember('019abc12-...', { roles: ['performer'] })
|
|
619
|
+
updated.effective_roles // ['admin', 'performer'] — what RLS auth.roles, hasRole() and realtime publisher_roles see
|
|
615
620
|
```
|
|
616
621
|
|
|
617
622
|
`role` is the only way to populate the RLS expression variable `auth.role` —
|
|
618
|
-
end-users can't set it on themselves through the public profile API.
|
|
623
|
+
end-users can't set it on themselves through the public profile API. `roles` adds more roles
|
|
624
|
+
on top of it (each up to 64 bytes, at most 16 including `role`).
|
|
619
625
|
|
|
620
626
|
### Database
|
|
621
627
|
|
|
@@ -978,6 +984,7 @@ const subscription = await cb.realtime.subscribe('chat-room')
|
|
|
978
984
|
// Listen for messages
|
|
979
985
|
subscription.onMessage((message) => {
|
|
980
986
|
console.log('New message:', message.data)
|
|
987
|
+
message.fromMember // sender's app member ID, verified by the server (v6.7.0+, member connections only)
|
|
981
988
|
})
|
|
982
989
|
|
|
983
990
|
// Send message
|
|
@@ -990,6 +997,39 @@ await subscription.unsubscribe()
|
|
|
990
997
|
await cb.realtime.disconnect()
|
|
991
998
|
```
|
|
992
999
|
|
|
1000
|
+
#### Who sent it: `fromMember` (v6.7.0)
|
|
1001
|
+
|
|
1002
|
+
`message.from` 은 보낸 연결이 접속할 때 스스로 정해 보내는 `client_id` 라 누구나 다른 값을 흉내 낼 수 있습니다. 서버가 검증한
|
|
1003
|
+
보낸 사람은 `message.fromMember`(앱 멤버 ID)입니다. 보낸 연결이 앱 멤버 `accessToken` 으로 접속했을 때만 있고, 익명(공개 키)
|
|
1004
|
+
연결이 보낸 메시지에는 없습니다. 실시간 메시지와 히스토리(`getHistory`) 모두에 실립니다.
|
|
1005
|
+
|
|
1006
|
+
```typescript
|
|
1007
|
+
chat.onMessage((m) => {
|
|
1008
|
+
if (!m.fromMember) return // 로그인 멤버의 메시지만
|
|
1009
|
+
addChat(m.fromMember, m.data) // 권한, 점수, 신고 판단은 fromMember 로 (data 안의 userId 는 위조 가능)
|
|
1010
|
+
})
|
|
1011
|
+
```
|
|
1012
|
+
|
|
1013
|
+
#### Rooms: `subscribe(category, { room })` (v6.7.0)
|
|
1014
|
+
|
|
1015
|
+
카테고리 안의 room 하나를 구독합니다. 서버 채널 이름은 `category:room` 이고 설정(발행 정책, 방송인 역할, persist 등)은
|
|
1016
|
+
베이스 카테고리의 것을 씁니다. 메시지는 같은 room 을 구독한 연결끼리만 오갑니다.
|
|
1017
|
+
|
|
1018
|
+
```typescript
|
|
1019
|
+
const stage = await cb.realtime.subscribe('concert', { room: 'show-1025' })
|
|
1020
|
+
stage.channel // 'concert:show-1025' — 이름을 받는 메서드(unsubscribe, markRead, onReadReceipt)에 넘기는 값
|
|
1021
|
+
stage.room // 'show-1025'
|
|
1022
|
+
await stage.send({ cheer: 1 }) // send / getHistory / unsubscribe / onMessage 는 모두 이 room 채널로 동작
|
|
1023
|
+
```
|
|
1024
|
+
|
|
1025
|
+
- room 은 영문, 숫자, `-`, `_`, `.` 로 1~128자, category 는 영문, 숫자, `-`, `_` 만 쓴 베이스 이름이어야 합니다. 어긋나면
|
|
1026
|
+
서버에 보내기 전에 에러를 던집니다. 문자열 `subscribe('concert:show-1025')` 도 예전처럼 동작합니다(검증은 서버가 합니다).
|
|
1027
|
+
- 재연결하면 room 채널도 다시 구독되고, 앱이 쥔 `info` 와 핸들러가 그대로 이어집니다.
|
|
1028
|
+
- 카테고리가 `require_room_auth` 이면 서버가 앱의 room 인가 함수에 구독과 발행을 묻습니다. 함수가
|
|
1029
|
+
`{ authorized: true, publisher: true }` 를 돌려주면 그 로그인 멤버는 그 room 의 방송인이 됩니다(아래 표의 방송인 규칙이 그
|
|
1030
|
+
room 에 적용). 발행 권한은 연결마다, room 마다 한 번만 묻고 기억합니다. 함수 계약은
|
|
1031
|
+
[실시간 문서](https://connectbase.world/docs/sdk/realtime)를 보세요.
|
|
1032
|
+
|
|
993
1033
|
#### Connection state (v6.0.0)
|
|
994
1034
|
|
|
995
1035
|
WS 가 차단된 망에서 SDK 는 SSE 폴백으로 내려갑니다. 이 모드에서는 AI 스트리밍만 되고
|
|
@@ -1030,7 +1070,9 @@ try {
|
|
|
1030
1070
|
호출 사이에 재사용되므로 닫지 않은 소켓이 호출마다 쌓입니다(`unsubscribe()` 는 소켓을 닫지 않습니다).
|
|
1031
1071
|
|
|
1032
1072
|
한 IP 에서 연결이 몰려 서버가 `rate_limited` 로 거부하면(v6.6.0+) `connect()` 가 서버가 알려 준 시간(`retryAfterMs`)에
|
|
1033
|
-
지터를 더해 스스로 다시 붙습니다.
|
|
1073
|
+
지터를 더해 스스로 다시 붙습니다. 인증 서버가 잠깐 닿지 않는 `auth_unavailable` 도 같습니다(v6.7.0+, 재시도 시각이 없는
|
|
1074
|
+
구버전 서버면 지수 백오프). 연결 제한 시간(`timeout`, 기본 30초) 안에 못 붙을 때만 이 에러를 던집니다. `unauthorized` 처럼
|
|
1075
|
+
다시 해도 소용없는 거부는 바로 던집니다.
|
|
1034
1076
|
|
|
1035
1077
|
#### Broadcast categories: publishers (v6.6.0)
|
|
1036
1078
|
|
|
@@ -1066,7 +1108,8 @@ try {
|
|
|
1066
1108
|
```
|
|
1067
1109
|
|
|
1068
1110
|
방송인 여부는 구독할 때 정해집니다. 멤버 역할이나 카테고리 설정을 바꾸면 다시 구독(또는 재연결)할 때 반영되고,
|
|
1069
|
-
카테고리 설정은 서버 캐시 때문에 최대 5분 뒤에 반영됩니다.
|
|
1111
|
+
카테고리 설정은 서버 캐시 때문에 최대 5분 뒤에 반영됩니다. room 채널(v6.7.0)은 room 인가 함수가 `publisher: true` 로
|
|
1112
|
+
room 마다 방송인을 정할 수도 있습니다(위 "Rooms").
|
|
1070
1113
|
|
|
1071
1114
|
#### Presence / Typing
|
|
1072
1115
|
|
package/dist/cli.js
CHANGED
|
@@ -156,7 +156,7 @@ function handleTunnelError(msg, appId, localPort) {
|
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
// src/cli.ts
|
|
159
|
-
var VERSION = "6.
|
|
159
|
+
var VERSION = "6.7.0" ? "6.7.0" : getPackageVersion();
|
|
160
160
|
var DEFAULT_BASE_URL = "https://api.connectbase.world";
|
|
161
161
|
var colors = {
|
|
162
162
|
reset: "\x1B[0m",
|