connectbase-client 6.0.1 → 6.3.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 +230 -4
- package/README.md +130 -13
- package/dist/cli.js +250 -168
- package/dist/connect-base.umd.js +5 -5
- package/dist/index.d.mts +964 -22
- package/dist/index.d.ts +964 -22
- package/dist/index.js +629 -7
- package/dist/index.mjs +628 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,232 @@
|
|
|
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.3.0] - 2026-09-07
|
|
7
|
+
|
|
8
|
+
### Fixed — Secret Key 가 만료되면 `init` 을 다시 돌릴 수 없던 문제
|
|
9
|
+
|
|
10
|
+
`init` 은 `.connectbaserc` 에 저장된 Secret Key 를 그대로 집어 씁니다. 그 키가 만료되거나
|
|
11
|
+
취소되면 앱 목록 조회가 401 을 받고 **그대로 종료**했는데, 다시 실행해도 같은 죽은 키를
|
|
12
|
+
다시 집어 오므로 같은 자리에서 또 죽었습니다. `.connectbaserc` 를 손으로 지우는 것 말고는
|
|
13
|
+
빠져나올 길이 없었습니다.
|
|
14
|
+
|
|
15
|
+
이제 401 을 받으면 **그 자리에서 재인증**합니다 (브라우저 로그인 또는 직접 입력). 새 키는
|
|
16
|
+
즉시 `.connectbaserc` 에 저장되고 `.mcp.json` 에도 반영되므로, 파일을 지우는 절차가
|
|
17
|
+
사라졌습니다. 비대화형(`--yes`)에서는 프롬프트 대신 새 키를 넘기라고 명확히 안내합니다.
|
|
18
|
+
|
|
19
|
+
`connectbase mcp` 도 같습니다 — 넘겨받은 키가 살아 있는지 **먼저 확인**한 뒤 `.mcp.json`
|
|
20
|
+
에 씁니다. 종전에는 만료된 키를 그대로 적고 "설정 완료" 라고 말해서, 실패가 Claude Code 의
|
|
21
|
+
401 로만 드러났습니다. (네트워크 오류로는 막지 않습니다 — 오프라인에서도 설정은 써집니다.)
|
|
22
|
+
|
|
23
|
+
재초기화 확인 프롬프트의 기본값도 "계속" 으로 바꿨습니다. 같은 앱을 고르면 Public Key 를
|
|
24
|
+
재사용하므로 재실행은 안전한 동작입니다.
|
|
25
|
+
|
|
26
|
+
### Fixed — `npm run deploy` 가 `command not found` 로 죽던 문제
|
|
27
|
+
|
|
28
|
+
`init` 이 `package.json` 에 deploy 스크립트만 넣고 CLI 를 의존성으로 선언하지 않아서,
|
|
29
|
+
SDK 가 설치되지 않은 프로젝트에서 마지막 안내대로 `npm run deploy` 를 치면
|
|
30
|
+
`connectbase: command not found` 가 났습니다. 이제 `devDependencies` 에
|
|
31
|
+
`connectbase-client` 를 함께 선언하고, 설치가 필요하면 마지막 안내에 `npm install` 을
|
|
32
|
+
포함합니다. (`npx --yes` 를 스크립트에 박는 우회는 쓰지 않았습니다 — 빌드마다
|
|
33
|
+
레지스트리에서 최신본을 끌어오면 재현 가능한 빌드가 아닙니다.)
|
|
34
|
+
|
|
35
|
+
빌드 스크립트가 있으면 deploy 스크립트는 이제 build 명령을 **복사하지 않고**
|
|
36
|
+
`npm run build && connectbase deploy <dir>` 로 위임합니다. 복사본은 build 가 바뀌어도
|
|
37
|
+
갱신되지 않아 조용히 낡습니다.
|
|
38
|
+
|
|
39
|
+
### Fixed — `--base-url` / `CONNECTBASE_BASE_URL` 이 `init` 에서 무시되던 문제
|
|
40
|
+
|
|
41
|
+
`init` 과 브라우저 로그인, SDK 문서 다운로드, MCP 설정이 상수를 직접 참조해, 로컬이나
|
|
42
|
+
스테이징 스택을 지정해도 프로덕션 API 로 갔습니다. 브라우저 로그인 URL 도 같은 환경을
|
|
43
|
+
가리키도록 API URL 에서 파생시킵니다.
|
|
44
|
+
|
|
45
|
+
### Fixed — CLI 버전을 빌드 시점에 주입
|
|
46
|
+
|
|
47
|
+
종전에는 런타임에 `package.json` 을 찾아 읽고, 실패하면 하드코딩된 옛 버전(`3.25.1`)으로
|
|
48
|
+
폴백했습니다. 그 폴백은 조용히 낡았고, 단순히 `--version` 이 틀리는 데서 끝나지 않았습니다 —
|
|
49
|
+
`init` 이 사용자의 `package.json` 에 적는 의존성 범위에까지 그 값이 들어갑니다.
|
|
50
|
+
|
|
51
|
+
### Fixed — git 저장소가 아닌 폴더에서 `fatal:` 오류가 출력되던 문제
|
|
52
|
+
|
|
53
|
+
`init` 이 프로젝트 루트를 찾을 때 쓰는 `git rev-parse` 의 stderr 가 그대로 새어 나와,
|
|
54
|
+
정상 경로인데도 화면에 `fatal: … 깃 저장소가 아닙니다` 가 세 번 찍혔습니다.
|
|
55
|
+
|
|
56
|
+
### Changed — 문서의 `npx` 예제가 패키지 이름을 쓰도록 통일
|
|
57
|
+
|
|
58
|
+
`connectbase` 는 이 패키지가 설치하는 **bin 이름**이지 npm 패키지 이름이 아닙니다. npx 는
|
|
59
|
+
로컬 `node_modules/.bin` 을 먼저 보고, 없으면 그 이름의 **패키지**를 레지스트리에서 받으려
|
|
60
|
+
합니다. 그래서 `npx connectbase init` 은 이 패키지가 이미 설치된 프로젝트에서만 동작하고,
|
|
61
|
+
아무것도 없는 새 폴더 — 즉 `init` 을 처음 실행하는 바로 그 상황 — 에서는 npm 404 가 났습니다.
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
$ npx connectbase init
|
|
65
|
+
npm error 404 Not Found - GET https://registry.npmjs.org/connectbase
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
전역 설치(`npm i -g connectbase-client`)로 PATH 에 `connectbase` 가 있어도 마찬가지입니다.
|
|
69
|
+
npx 는 PATH 를 보지 않습니다.
|
|
70
|
+
|
|
71
|
+
규칙을 하나로 정리했습니다:
|
|
72
|
+
|
|
73
|
+
| 형태 | 이름 | 왜 |
|
|
74
|
+
|---|---|---|
|
|
75
|
+
| `npx …` | `connectbase-client` | npx 인자는 **패키지** 이름이다. 설치 여부와 무관하게 항상 동작 |
|
|
76
|
+
| 전역 설치 후 / `npm run` 스크립트 / `node_modules/.bin` | `connectbase` | 짧은 bin 이름. 0.16.1 에서 정한 용도 그대로 |
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
npx connectbase-client init # 설치 전 — 패키지 이름
|
|
80
|
+
npm install # init 이 devDependencies 에 넣어 둔 것을 설치
|
|
81
|
+
npm run deploy # 내부적으로 `connectbase deploy ./dist` — 짧은 이름 그대로
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`connectbase` 를 실제 패키지로도 발행하려 했으나, npm 이 2013 년의 `connect-base` 와
|
|
85
|
+
이름이 유사하다는 이유로 거절했습니다 (`@connectbase` 스코프도 타인이 사용 중). 이름을
|
|
86
|
+
확보하면 `npx connectbase` 도 함께 동작하게 할 예정입니다.
|
|
87
|
+
|
|
88
|
+
## [6.2.0] - 2026-09-06
|
|
89
|
+
|
|
90
|
+
### Added — 웹 스토리지 관리 API 에 Secret Key 전송
|
|
91
|
+
|
|
92
|
+
`deploy`, `promote`, `releases` 와 `init` 의 스토리지 목록/생성이 이제 Secret Key(`cb_sk_*`)를
|
|
93
|
+
함께 보냅니다. `.connectbaserc` 의 `secretKey` 또는 `CONNECTBASE_SECRET_KEY` 환경변수에서
|
|
94
|
+
자동으로 읽습니다. **`init` 을 이미 실행했다면 할 일이 없습니다.**
|
|
95
|
+
|
|
96
|
+
**왜 필요한가.** Public Key(`cb_pk_*`)는 비밀이 아닙니다. 브라우저 번들에 실려 배포되는 공개
|
|
97
|
+
식별자이고, 실제로 운영 중인 사이트의 JS 에서 그대로 추출됩니다. 그런데 이 값 하나로 배포와
|
|
98
|
+
production 승격이 가능했습니다 — 공개된 사이트에서 키를 뽑은 제3자가 그 사이트를 통째로
|
|
99
|
+
교체할 수 있었다는 뜻입니다. Public Key 는 "어느 앱인가" 를 말할 뿐 "이 앱을 다룰 자격이
|
|
100
|
+
있는가" 를 증명하지 못하므로, 쓰기 동작의 자격증명이 될 수 없습니다.
|
|
101
|
+
|
|
102
|
+
**서버는 곧 이를 요구합니다.** 현재는 기존 배포 파이프라인이 끊기지 않도록 유예 중이고,
|
|
103
|
+
이 버전이 충분히 퍼진 뒤 강제로 전환합니다. 그때부터 Secret Key 없는 호출은 401
|
|
104
|
+
`SECRET_KEY_REQUIRED` 로 거부됩니다.
|
|
105
|
+
|
|
106
|
+
**이 버전은 아무것도 깨뜨리지 않습니다.** Secret Key 가 없으면 경고만 하고 그대로 진행합니다.
|
|
107
|
+
CLI 가 서버보다 먼저 조이면, 우리가 생성해 드리는 GitHub Actions 워크플로처럼 버전을 고정하지
|
|
108
|
+
않는(`npx --yes connectbase-client deploy`) 환경이 서버 변경 전에 깨지기 때문입니다.
|
|
109
|
+
|
|
110
|
+
**지금 해두실 것.** CI 를 쓰신다면 저장소 시크릿에 `CONNECTBASE_SECRET_KEY` 를 추가하고
|
|
111
|
+
워크플로 env 에 넣으세요. 워크플로 파일에 값을 직접 적으면 저장소에 커밋됩니다.
|
|
112
|
+
|
|
113
|
+
```yaml
|
|
114
|
+
env:
|
|
115
|
+
CONNECTBASE_PUBLIC_KEY: ${{ secrets.CONNECTBASE_PUBLIC_KEY }}
|
|
116
|
+
CONNECTBASE_SECRET_KEY: ${{ secrets.CONNECTBASE_SECRET_KEY }}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
`page-metas` 계열 API 는 **변경 없습니다** — 브라우저 SDK 의 정규 경로라 그대로 둡니다.
|
|
120
|
+
|
|
121
|
+
### Added — `X-User-Secret-Key` 전용 헤더
|
|
122
|
+
|
|
123
|
+
Secret Key 를 `Authorization` 대신 전용 헤더로도 보냅니다.
|
|
124
|
+
|
|
125
|
+
`Authorization` 자리는 AppMember 세션 토큰이 먼저 차지합니다. 종전 SDK 는 그 헤더가 이미
|
|
126
|
+
있으면 Secret Key 를 붙이지 않았기 때문에, **로그인 상태 브라우저에서는 `secretKey` 를 설정해도
|
|
127
|
+
전송되지 않았습니다.** 두 자격증명은 역할이 다릅니다 — 멤버 토큰은 "누가 이 앱의 회원인가",
|
|
128
|
+
Secret Key 는 "이 호출자가 앱을 관리할 자격이 있는가". 한 헤더를 두고 다투게 두면 관리 동작이
|
|
129
|
+
로그인 상태에서만 조용히 실패합니다.
|
|
130
|
+
|
|
131
|
+
서버는 전용 헤더를 `Authorization` 보다 우선해 읽습니다. 기존 `Authorization` 경로도 그대로
|
|
132
|
+
보내므로 구버전 서버와 호환됩니다.
|
|
133
|
+
|
|
134
|
+
> Secret Key 는 서버사이드 자격증명입니다. 브라우저 번들에 넣지 마세요.
|
|
135
|
+
|
|
136
|
+
### Fixed — 대용량 파일 배포 시 원인 불명 500
|
|
137
|
+
|
|
138
|
+
파일당 저장 한도(약 6 MiB, 바이너리는 base64 로 약 1.33배)를 넘으면 서버가 원시 DB 오류를
|
|
139
|
+
500 으로 뭉개 반환했습니다. 이제 **413** 과 함께 초과 파일 목록, 원본 크기 추정치, presign
|
|
140
|
+
경로 안내가 응답에 담깁니다. CLI 는 종전대로 대용량 바이너리를 자동으로 오프로드합니다.
|
|
141
|
+
|
|
142
|
+
## [6.1.0] - 2026-09-01
|
|
143
|
+
|
|
144
|
+
### Added — `cb.organizations.*`: 조직/팀(워크스페이스)
|
|
145
|
+
|
|
146
|
+
앱의 **엔드유저가 만드는 조직**을 다루는 모듈입니다. Connect Base 콘솔의 협업자 RBAC
|
|
147
|
+
(`cb.roles.*`)와는 완전히 별개 시스템입니다.
|
|
148
|
+
|
|
149
|
+
| 메서드 | 설명 |
|
|
150
|
+
|---|---|
|
|
151
|
+
| `create` / `listMine` / `get` / `update` / `delete` | 조직 CRUD (`slug` 는 생성 후 변경 불가) |
|
|
152
|
+
| `listMembers` / `updateMemberRole` / `removeMember` | 멤버 관리 (본인 ID 로 `removeMember` 하면 탈퇴) |
|
|
153
|
+
| `createInvitation` / `listInvitations` / `revokeInvitation` / `acceptInvitation` | 초대 |
|
|
154
|
+
| `switchTo` | 조직 전환 — 조직 컨텍스트가 실린 새 액세스 토큰을 받아 SDK 에 자동 적용 |
|
|
155
|
+
|
|
156
|
+
모든 조직 API 는 **로그인한 회원 토큰**을 요구합니다. 퍼블릭 키만으로 호출하면 401 입니다 —
|
|
157
|
+
퍼블릭 키는 설계상 클라이언트 번들에 노출되는 값이라, 그것만으로 목록을 열면 앱의 조직 구조가
|
|
158
|
+
통째로 덤프되기 때문입니다.
|
|
159
|
+
|
|
160
|
+
> #### 조직 컨텍스트는 10분마다 갱신해야 합니다
|
|
161
|
+
>
|
|
162
|
+
> `switchTo()` 가 심는 RLS 조직 컨텍스트(`auth.org_id` / `auth.org_role`)의 수명은 액세스
|
|
163
|
+
> 토큰(1시간)과 **독립적인 10분**입니다. 만료되어도 토큰 자체는 유효해 401 이 나지 않고,
|
|
164
|
+
> 조직 규칙만 fail-closed 로 거부되어 **"멀쩡하던 조회가 갑자기 전부 막히는"** 증상으로만
|
|
165
|
+
> 드러납니다. 반환값의 `org_context_expires_in`(초) 보다 먼저 다시 호출하세요.
|
|
166
|
+
> 토큰 회전(refresh)이 일어나도 조직 컨텍스트는 의도적으로 사라지므로 다시 호출해야 합니다.
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
let timer: ReturnType<typeof setTimeout> | undefined
|
|
170
|
+
|
|
171
|
+
async function activate(orgId: string) {
|
|
172
|
+
const ctx = await cb.organizations.switchTo(orgId)
|
|
173
|
+
clearTimeout(timer)
|
|
174
|
+
timer = setTimeout(
|
|
175
|
+
() => activate(orgId),
|
|
176
|
+
Math.max(ctx.org_context_expires_in - 60, 30) * 1000,
|
|
177
|
+
)
|
|
178
|
+
return ctx
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Changed — `subscription.deleteBillingKey()` 가 삭제 결과를 반환합니다
|
|
183
|
+
|
|
184
|
+
반환 타입이 `Promise<void>` 에서 `Promise<DeleteBillingKeyResponse>` 로 바뀌었습니다.
|
|
185
|
+
서버는 이전부터 이 본문을 보내고 있었지만 타입에 노출되지 않아, **"PG 등록이 남아 있다"는
|
|
186
|
+
경고를 SDK 사용자가 볼 수 없었습니다.**
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
const result = await cb.subscription.deleteBillingKey(billingKeyId)
|
|
190
|
+
if (!result.provider_revoked) {
|
|
191
|
+
// Connect Base 기록만 지워졌고 PG 등록은 그대로 남아 있다
|
|
192
|
+
alert(result.provider_revoke_note)
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
| 프로바이더 | PG 해지 | 동작 |
|
|
197
|
+
|---|---|---|
|
|
198
|
+
| `toss` | O | 토스 빌링키 삭제 API 호출. PG 삭제가 실패하면 DB 행도 지우지 않고 에러 |
|
|
199
|
+
| `payapp` / `paypal` / `paddle` / `stripe` | X | "결제수단 삭제" API 가 없어 기록만 삭제. `provider_revoked: false` + `provider_revoke_note` |
|
|
200
|
+
|
|
201
|
+
반환값을 쓰지 않던 기존 호출은 그대로 동작합니다 (하위호환).
|
|
202
|
+
|
|
203
|
+
### Added — `DeleteBillingKeyResponse` 타입
|
|
204
|
+
|
|
205
|
+
`message` / `provider` / `provider_revoked` / `provider_revoke_note?`.
|
|
206
|
+
|
|
207
|
+
### Added — 퍼블릭 키 스코프와 무중단 회전
|
|
208
|
+
|
|
209
|
+
- `cb.publicKey.createPublicKey()` 가 `scopes` 를 받습니다. **비우면 전권**이고(기존 키가 전부
|
|
210
|
+
그렇습니다), 어휘는 새로 만든 것이 아니라 콘솔 RBAC 권한 이름과 service_role
|
|
211
|
+
`management_scopes` 의 합집합입니다.
|
|
212
|
+
- `PublicKeyItem` / `CreatePublicKeyResponse` / `UpdatePublicKeyResponse` 에 `scopes` 가 실립니다.
|
|
213
|
+
`PublicKeyItem` 에는 발급 출처 감사 필드(`created_by_kind`, `created_by_user_id`,
|
|
214
|
+
`created_ip_masked`, `created_user_agent`)도 추가됐습니다.
|
|
215
|
+
- **`cb.publicKey.rotatePublicKey(appId, keyId, { grace_period_hours?, name? })`** 신설 —
|
|
216
|
+
새 키를 발급하고 옛 키는 유예 기간(기본 24시간, `0` = 즉시, 상한 720시간) 뒤 만료됩니다.
|
|
217
|
+
옛 키의 스코프와 `payment_mode` 는 새 키가 물려받습니다. 반환값의 `new_key.key` 는 이때만
|
|
218
|
+
볼 수 있고, `previous_key_expires_at` 전에 클라이언트 배포를 끝내야 합니다.
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
const rotated = await cb.publicKey.rotatePublicKey('app-id', 'key-id', { grace_period_hours: 72 })
|
|
222
|
+
console.log(rotated.new_key.key, rotated.previous_key_expires_at)
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Added — `HttpClient.setAccessToken()`
|
|
226
|
+
|
|
227
|
+
refresh token 을 건드리지 않고 액세스 토큰만 교체합니다. 조직 컨텍스트 발급처럼 **새 액세스
|
|
228
|
+
토큰만 돌려주는** 엔드포인트를 위한 것으로, `cb.organizations.switchTo()` 가 내부에서 씁니다.
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
6
232
|
## [6.0.1] - 2026-08-22
|
|
7
233
|
|
|
8
234
|
### Fixed — 로그인 세션이 없을 때 `/v1/auth/re-issue` 401 이 반복해서 나가던 문제
|
|
@@ -410,7 +636,7 @@ await ctx.cbAdmin.appMembers.remove(ctx.appId, memberId)
|
|
|
410
636
|
```bash
|
|
411
637
|
# 이제 dist 에 두기만 하면 함께 올라간다
|
|
412
638
|
echo "/blog /blog.html 200" > dist/_redirects
|
|
413
|
-
npx connectbase deploy ./dist
|
|
639
|
+
npx connectbase-client deploy ./dist
|
|
414
640
|
```
|
|
415
641
|
|
|
416
642
|
- `_redirects`, `_headers` 만 이름 완전일치로 허용한다. `Dockerfile`·`Makefile`·`LICENSE`
|
|
@@ -1008,7 +1234,7 @@ AI 에이전트(Claude Code 등)가 `init` 을 직접 실행할 수 있도록
|
|
|
1008
1234
|
자동 정리(레거시 fullstack 파일 포함), `deploy` 성공 후 변경이 있을 때만 조용히 자동 갱신
|
|
1009
1235
|
(`--skip-docs` 로 생략 가능).
|
|
1010
1236
|
- **CLAUDE.md @import**: 참조 블록이 `@.claude/docs/project-rules.md` import 로 규칙을 매
|
|
1011
|
-
세션 자동 로드. MCP 미연결 시 `npx connectbase mcp` 안내 지시 포함. `mcp` 명령도 비대화형
|
|
1237
|
+
세션 자동 로드. MCP 미연결 시 `npx connectbase-client mcp` 안내 지시 포함. `mcp` 명령도 비대화형
|
|
1012
1238
|
지원(`--secret-key`/env/.connectbaserc 자동 사용).
|
|
1013
1239
|
|
|
1014
1240
|
### Changed / Security
|
|
@@ -2649,7 +2875,7 @@ ComfyUI × 웹스토리지 같은 e2e 통합 패턴 (작업 제출 → 폴링
|
|
|
2649
2875
|
|
|
2650
2876
|
### Fixed
|
|
2651
2877
|
|
|
2652
|
-
- `npx connectbase docs` 가 인증 없이 곧장 문서를 받도록 수정. 백엔드 `/v1/storages/webs/claude-md`
|
|
2878
|
+
- `npx connectbase-client docs` 가 인증 없이 곧장 문서를 받도록 수정. 백엔드 `/v1/storages/webs/claude-md`
|
|
2653
2879
|
는 public 라우트인데 CLI 가 불필요하게 브라우저 인증 → 앱 선택 → Public Key 발급을 강제하던
|
|
2654
2880
|
흐름을 제거. 캐시된 publicKey 가 있으면 문서에 박아주고, 없으면 백엔드가 placeholder
|
|
2655
2881
|
(`YOUR_PUBLIC_KEY_HERE`) 로 대체해 그대로 다운로드.
|
|
@@ -3081,7 +3307,7 @@ sisun 팀(`019d85c7-...`) 의 운영 피드백 3건이 시작점이며, 백엔
|
|
|
3081
3307
|
|
|
3082
3308
|
## [1.4.2] - 2026-04-18
|
|
3083
3309
|
|
|
3084
|
-
`npx connectbase docs` 명령이 init/deploy/tunnel 과 달리 brower auth 흐름을 거치지 않아, Public Key 가 없는 사용자가 키 발급 경로를 모른 채 prompt 만 보던 UX 문제 해결.
|
|
3310
|
+
`npx connectbase-client docs` 명령이 init/deploy/tunnel 과 달리 brower auth 흐름을 거치지 않아, Public Key 가 없는 사용자가 키 발급 경로를 모른 채 prompt 만 보던 UX 문제 해결.
|
|
3085
3311
|
|
|
3086
3312
|
### Fixed
|
|
3087
3313
|
|
package/README.md
CHANGED
|
@@ -27,13 +27,19 @@ Connect Base provides **two types** of Keys. Use the right key for your use case
|
|
|
27
27
|
|---------|----------|---------|
|
|
28
28
|
| Frontend SDK (`new ConnectBase()`) | **Public Key** (`cb_pk_`) | Web/app: DB queries, auth, file uploads |
|
|
29
29
|
| `.env` file (`VITE_CONNECTBASE_PUBLIC_KEY`) | **Public Key** (`cb_pk_`) | React, Vue, etc. |
|
|
30
|
-
| CLI deploy (`.connectbaserc`) | **Public Key** (`cb_pk_`) | `npx connectbase deploy` |
|
|
30
|
+
| CLI deploy (`.connectbaserc`) | **Public Key + Secret Key** (`cb_pk_` + `cb_sk_`) | `npx connectbase-client deploy` |
|
|
31
31
|
| MCP server (AI tools) | **Secret Key** (`cb_sk_`) | Claude, Cursor, Windsurf |
|
|
32
32
|
| Server-side admin tasks | **Secret Key** (`cb_sk_`) | Backend full data access |
|
|
33
33
|
|
|
34
34
|
> ⚠️ **MCP server rejects Public Keys** — you must use a Secret Key (`cb_sk_`).
|
|
35
35
|
>
|
|
36
36
|
> ⚠️ **Never use Secret Keys in frontend code** — RLS is bypassed, exposing all data.
|
|
37
|
+
>
|
|
38
|
+
> 🔐 **Web storage deploys need both keys.** A Public Key ships inside your browser bundle, so
|
|
39
|
+
> anyone can read it out of a deployed site's JS — it cannot prove you may replace that site.
|
|
40
|
+
> The web storage list/create/deploy/promote routes therefore require
|
|
41
|
+
> `Authorization: Bearer cb_sk_*` **in addition to** `X-Public-Key`; without it they return
|
|
42
|
+
> **401 `SECRET_KEY_REQUIRED`**. The CLI sends it for you — see [CLI](#cli).
|
|
37
43
|
|
|
38
44
|
Create Keys in the Console under **Settings > API tab**. Choose Public or Secret type when creating. The full key is shown **only once** at creation time.
|
|
39
45
|
|
|
@@ -113,6 +119,7 @@ try {
|
|
|
113
119
|
- **Push Notifications**: Cross-platform push notification support
|
|
114
120
|
- **WebRTC**: Real-time audio/video communication
|
|
115
121
|
- **Payments**: Subscription and one-time payment support
|
|
122
|
+
- **Organizations**: End-user workspaces/teams with invitations and an RLS organization context (`cb.organizations.*`)
|
|
116
123
|
- **AI Streaming**: Real-time AI text generation via WebSocket (multi-provider: Gemini, OpenAI, Claude, Ollama, LM Studio, OpenAI-compatible)
|
|
117
124
|
- **Knowledge Base (RAG)**: Document indexing + BM25 search with nori 한국어 형태소. PDF / DOCX / text file upload via `addDocumentFromFile`
|
|
118
125
|
- **Endpoint**: Call your own GPU models on your own PC through one `cb_pk_*` key — ConnectBase forwards the payload as-is (dumb pipe)
|
|
@@ -124,20 +131,37 @@ try {
|
|
|
124
131
|
|
|
125
132
|
Deploy your web application to Connect Base Web Storage with a single command.
|
|
126
133
|
|
|
134
|
+
> **After `npx`, always use the package name `connectbase-client`.** This package installs two
|
|
135
|
+
> bins — `connectbase` and `connectbase-client` — but `connectbase` is a *bin* name, not a
|
|
136
|
+
> package name. npx checks the local `node_modules/.bin` first and otherwise fetches a
|
|
137
|
+
> **package** by that name, so `npx connectbase init` only works in a project that already has
|
|
138
|
+
> this package installed; in an empty folder it fails with npm 404. A global install does not
|
|
139
|
+
> help — npx does not look at `PATH`.
|
|
140
|
+
>
|
|
141
|
+
> Use the short `connectbase` **after** installing: globally, inside `npm run` scripts, or via
|
|
142
|
+
> `node_modules/.bin`.
|
|
143
|
+
>
|
|
144
|
+
> ```bash
|
|
145
|
+
> npx connectbase-client init # nothing installed yet — package name
|
|
146
|
+
> npm install # installs what init added to devDependencies
|
|
147
|
+
> npm run deploy # runs `connectbase deploy ./dist` — short name is fine here
|
|
148
|
+
> ```
|
|
149
|
+
|
|
127
150
|
### Quick Start
|
|
128
151
|
|
|
129
152
|
```bash
|
|
130
153
|
# 1. Initialize (one-time setup)
|
|
131
|
-
npx connectbase init
|
|
154
|
+
npx connectbase-client init
|
|
132
155
|
|
|
133
156
|
# 2. Deploy
|
|
134
157
|
npm run deploy
|
|
135
158
|
```
|
|
136
159
|
|
|
137
160
|
The `init` command will:
|
|
138
|
-
- Ask for your
|
|
161
|
+
- Ask for your Secret Key (`cb_sk_`) — or take it from `--secret-key` / `CONNECTBASE_SECRET_KEY`
|
|
162
|
+
- Issue a Public Key for the app
|
|
139
163
|
- List existing web storages or create a new one automatically
|
|
140
|
-
- Create a `.connectbaserc` config file
|
|
164
|
+
- Create a `.connectbaserc` config file (holds both keys)
|
|
141
165
|
- Add `.connectbaserc` to `.gitignore`
|
|
142
166
|
- Add a `deploy` script to `package.json` (includes `build` if available)
|
|
143
167
|
|
|
@@ -154,7 +178,14 @@ The `init` command will:
|
|
|
154
178
|
If you prefer not to use `init`, you can pass options directly:
|
|
155
179
|
|
|
156
180
|
```bash
|
|
157
|
-
npx connectbase deploy ./dist -s <storage-id> -k <public-key>
|
|
181
|
+
npx connectbase-client deploy ./dist -s <storage-id> -k <public-key> --secret-key <secret-key>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
The Secret Key can also come from `CONNECTBASE_SECRET_KEY` — preferred in CI, so it never
|
|
185
|
+
appears in shell history or CI logs:
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
CONNECTBASE_SECRET_KEY=cb_sk_... npx connectbase-client deploy ./dist -s <storage-id> -k <public-key>
|
|
158
189
|
```
|
|
159
190
|
|
|
160
191
|
### Options
|
|
@@ -162,7 +193,8 @@ npx connectbase deploy ./dist -s <storage-id> -k <public-key>
|
|
|
162
193
|
| Option | Alias | Description |
|
|
163
194
|
|--------|-------|-------------|
|
|
164
195
|
| `--storage <id>` | `-s` | Storage ID |
|
|
165
|
-
| `--public-key <key>` | `-k` |
|
|
196
|
+
| `--public-key <key>` | `-k` | Public Key (`cb_pk_`) — identifies the app |
|
|
197
|
+
| `--secret-key <key>` | | Secret Key (`cb_sk_`) — required by `deploy` / `promote` / storage list & create. Prefer `CONNECTBASE_SECRET_KEY` in CI |
|
|
166
198
|
| `--base-url <url>` | `-u` | Custom server URL |
|
|
167
199
|
| `--timeout <sec>` | `-t` | Tunnel request timeout in seconds (tunnel only) |
|
|
168
200
|
| `--max-body <MB>` | | Tunnel max body size in MB (tunnel only) |
|
|
@@ -177,14 +209,14 @@ Expose a local server to the internet through a secure WebSocket tunnel. Useful
|
|
|
177
209
|
|
|
178
210
|
```bash
|
|
179
211
|
# Expose local port 8084 to the internet
|
|
180
|
-
npx connectbase tunnel 8084 -k <public-key>
|
|
212
|
+
npx connectbase-client tunnel 8084 -k <public-key>
|
|
181
213
|
|
|
182
214
|
# With environment variable
|
|
183
215
|
export CONNECTBASE_PUBLIC_KEY=your-public-key
|
|
184
|
-
npx connectbase tunnel 8084
|
|
216
|
+
npx connectbase-client tunnel 8084
|
|
185
217
|
|
|
186
218
|
# For GPU servers or long-running tasks (e.g., image generation)
|
|
187
|
-
npx connectbase tunnel 7860 --timeout 300 --max-body 50
|
|
219
|
+
npx connectbase-client tunnel 7860 --timeout 300 --max-body 50
|
|
188
220
|
```
|
|
189
221
|
|
|
190
222
|
The tunnel creates a public URL like `https://tunnel.connectbase.world/<tunnel-id>/` that proxies all HTTP requests to your local service.
|
|
@@ -214,7 +246,7 @@ on the server, so your SDK only needs the Public Key.
|
|
|
214
246
|
|
|
215
247
|
```bash
|
|
216
248
|
# Start ComfyUI on port 8188, expose it as endpoint label "comfyui-main"
|
|
217
|
-
npx connectbase tunnel 8188 --label comfyui-main --description "ComfyUI on my desktop"
|
|
249
|
+
npx connectbase-client tunnel 8188 --label comfyui-main --description "ComfyUI on my desktop"
|
|
218
250
|
```
|
|
219
251
|
|
|
220
252
|
Authentication uses your User Secret Key (`cb_sk_*`); the CLI calls the dual-auth
|
|
@@ -228,18 +260,35 @@ The `init` command creates `.connectbaserc` automatically. You can also create i
|
|
|
228
260
|
|
|
229
261
|
```json
|
|
230
262
|
{
|
|
231
|
-
"publicKey": "
|
|
263
|
+
"publicKey": "cb_pk_your-public-key",
|
|
264
|
+
"secretKey": "cb_sk_your-secret-key",
|
|
232
265
|
"storageId": "your-storage-id",
|
|
233
266
|
"deployDir": "./dist"
|
|
234
267
|
}
|
|
235
268
|
```
|
|
236
269
|
|
|
270
|
+
> ⚠️ `.connectbaserc` now holds a Secret Key — keep it in `.gitignore` (`init` adds it) and never
|
|
271
|
+
> commit it. On a shared machine or in CI, drop `secretKey` from the file and pass
|
|
272
|
+
> `CONNECTBASE_SECRET_KEY` instead.
|
|
273
|
+
|
|
237
274
|
### Environment Variables
|
|
238
275
|
|
|
239
276
|
```bash
|
|
240
|
-
export CONNECTBASE_PUBLIC_KEY=
|
|
277
|
+
export CONNECTBASE_PUBLIC_KEY=cb_pk_your-public-key
|
|
278
|
+
export CONNECTBASE_SECRET_KEY=cb_sk_your-secret-key # required by deploy / promote
|
|
241
279
|
export CONNECTBASE_STORAGE_ID=your-storage-id
|
|
242
|
-
npx connectbase deploy ./dist
|
|
280
|
+
npx connectbase-client deploy ./dist
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
In CI, read the Secret Key from a repository secret:
|
|
284
|
+
|
|
285
|
+
```yaml
|
|
286
|
+
# GitHub Actions
|
|
287
|
+
- run: npx connectbase-client deploy ./dist
|
|
288
|
+
env:
|
|
289
|
+
CONNECTBASE_PUBLIC_KEY: ${{ vars.CONNECTBASE_PUBLIC_KEY }}
|
|
290
|
+
CONNECTBASE_SECRET_KEY: ${{ secrets.CONNECTBASE_SECRET_KEY }}
|
|
291
|
+
CONNECTBASE_STORAGE_ID: ${{ vars.CONNECTBASE_STORAGE_ID }}
|
|
243
292
|
```
|
|
244
293
|
|
|
245
294
|
### Requirements
|
|
@@ -1330,6 +1379,20 @@ call.disconnect() // voice 는 그대로 유지됩니다
|
|
|
1330
1379
|
|
|
1331
1380
|
### Payments & Subscriptions
|
|
1332
1381
|
|
|
1382
|
+
> **빌링키 API 6종(발급/확인/목록/상세/수정/삭제)은 로그인한 회원 토큰이 필요합니다.**
|
|
1383
|
+
> `X-Public-Key` 단독 호출은 `401` 입니다. 로그인 없이 빌링키를 만들던 게스트 체크아웃 흐름은
|
|
1384
|
+
> 회원 로그인 후 호출로 바꿔야 합니다. 서버에서는 `cb_sk_*` 또는 `management_scopes` 에
|
|
1385
|
+
> `payment:read` / `subscription:manage` 를 opt-in 한 `service_role` 함수를 쓰세요.
|
|
1386
|
+
|
|
1387
|
+
```typescript
|
|
1388
|
+
// 결제수단 삭제 — PG 등록까지 지워졌는지는 프로바이더마다 다르다 (v6.1.0+)
|
|
1389
|
+
const deleted = await cb.subscription.deleteBillingKey('billing-key-1')
|
|
1390
|
+
if (!deleted.provider_revoked) {
|
|
1391
|
+
// toss 외 프로바이더: Connect Base 기록만 지워졌고 PG 등록은 남아 있다
|
|
1392
|
+
console.warn(deleted.provider_revoke_note)
|
|
1393
|
+
}
|
|
1394
|
+
```
|
|
1395
|
+
|
|
1333
1396
|
```typescript
|
|
1334
1397
|
// Create a subscription (정기 결제)
|
|
1335
1398
|
const subscription = await cb.subscription.create({
|
|
@@ -1384,6 +1447,60 @@ export async function handler(payload, ctx) {
|
|
|
1384
1447
|
얹힙니다. payapp/paypal 은 PG 가 결제일 변경 API 를 주지 않아 400 `next_billing_date_unsupported`
|
|
1385
1448
|
입니다 (로컬만 미루면 원래 날짜에 그대로 출금되므로 조용히 처리하지 않습니다).
|
|
1386
1449
|
|
|
1450
|
+
### Organizations (Teams / Workspaces)
|
|
1451
|
+
|
|
1452
|
+
앱의 **엔드유저가 만드는 조직**. Connect Base 콘솔의 협업자 RBAC(`cb.roles.*`)와는 별개
|
|
1453
|
+
시스템이다. **모든 조직 API 는 로그인한 회원 토큰을 요구한다** — 퍼블릭 키 단독 호출은 401 이다.
|
|
1454
|
+
|
|
1455
|
+
```typescript
|
|
1456
|
+
// 조직 만들기 (만든 사람이 owner)
|
|
1457
|
+
const org = await cb.organizations.create({ name: '우리 팀' })
|
|
1458
|
+
|
|
1459
|
+
// 초대 — 평문 토큰은 이 응답에서만 볼 수 있고, 메일 발송은 앱이 직접 한다
|
|
1460
|
+
const { token } = await cb.organizations.createInvitation(org.id, {
|
|
1461
|
+
email: 'teammate@example.com',
|
|
1462
|
+
role: 'member', // owner | admin | member
|
|
1463
|
+
})
|
|
1464
|
+
|
|
1465
|
+
// 초대 수락 (초대받은 계정으로 로그인한 상태에서)
|
|
1466
|
+
await cb.organizations.acceptInvitation(token)
|
|
1467
|
+
|
|
1468
|
+
// 내 조직 목록 (조직 전환 UI)
|
|
1469
|
+
const mine = await cb.organizations.listMine()
|
|
1470
|
+
```
|
|
1471
|
+
|
|
1472
|
+
#### 조직 컨텍스트는 10분마다 갱신해야 한다
|
|
1473
|
+
|
|
1474
|
+
데이터베이스 보안 규칙(RLS)의 `auth.org_id` / `auth.org_role` 은 **토큰에 실린 조직 컨텍스트**
|
|
1475
|
+
에서 온다. 이 컨텍스트의 수명은 액세스 토큰(1시간)과 **독립적인 10분**이다. 만료되어도 토큰
|
|
1476
|
+
자체는 유효해 401 이 나지 않고, 조직 규칙만 fail-closed 로 거부되어 **"멀쩡하던 조회가 갑자기
|
|
1477
|
+
전부 막히는"** 증상으로만 드러난다.
|
|
1478
|
+
|
|
1479
|
+
```typescript
|
|
1480
|
+
let timer: ReturnType<typeof setTimeout> | undefined
|
|
1481
|
+
|
|
1482
|
+
async function activate(orgId: string) {
|
|
1483
|
+
// 새 액세스 토큰을 받아 SDK 에 자동 적용한다
|
|
1484
|
+
const ctx = await cb.organizations.switchTo(orgId)
|
|
1485
|
+
clearTimeout(timer)
|
|
1486
|
+
// 만료 60초 전에 갱신
|
|
1487
|
+
timer = setTimeout(() => activate(orgId), Math.max(ctx.org_context_expires_in - 60, 30) * 1000)
|
|
1488
|
+
return ctx
|
|
1489
|
+
}
|
|
1490
|
+
```
|
|
1491
|
+
|
|
1492
|
+
토큰 회전(refresh)이 일어나면 조직 컨텍스트는 의도적으로 사라지므로 `switchTo()` 를 다시
|
|
1493
|
+
호출해야 한다. 내가 속하지 않은 조직은 403 이 아니라 **404** 다 (조직 ID 열거 방지).
|
|
1494
|
+
|
|
1495
|
+
RLS 규칙에서 쓸 수 있는 축:
|
|
1496
|
+
|
|
1497
|
+
| 축 | 조직 컨텍스트가 없을 때 | 용도 |
|
|
1498
|
+
|---|---|---|
|
|
1499
|
+
| `auth.org_id` / `auth.org_role` | **에러 (fail-closed)** | `data.org_id == auth.org_id` 같은 스칼라 비교 |
|
|
1500
|
+
| `auth.has_org` | `false` (에러 안 남) | 조직 유무로 분기 |
|
|
1501
|
+
| `hasOrgRole('owner')` | `false` (에러 안 남) | 조직 역할 판정 (앱 전역 `hasRole()` 과 다름) |
|
|
1502
|
+
| `inOrg('<uuid>')` | `false` (에러 안 남) | 특정 조직인지, 인자 생략 시 컨텍스트 유무 |
|
|
1503
|
+
|
|
1387
1504
|
### Support (End-user Issue Reporting)
|
|
1388
1505
|
|
|
1389
1506
|
End-user 가 앱 운영자에게 직접 버그·질문·요청을 발행하는 채널. 운영자 콘솔의 inbox 에 들어가며, AI 가 자동으로 요약·긴급도·카테고리를 분류한다 (운영자가 AI config 등록 시).
|