connectbase-client 6.6.1 → 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 CHANGED
@@ -3,6 +3,87 @@
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
+
6
87
  ## [6.6.1] - 2026-10-21
7
88
 
8
89
  ### Fixed — 패키지 메타데이터(코드 변경 없음)
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
- 지터를 더해 스스로 다시 붙습니다. 연결 제한 시간(`timeout`, 기본 30초) 안에 못 붙을 때만 이 에러를 던집니다.
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.6.1" ? "6.6.1" : getPackageVersion();
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",