connectbase-client 3.22.0 → 3.23.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,46 @@
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
+ ## [3.23.0] - 2026-05-26
7
+
8
+ ### Fixed — OAuth 표준 흐름 페이지 리로드 후 세션 유실 (platform-issue 019e638d)
9
+
10
+ 문서의 OAuth redirect 표준 예제 그대로 따라가도 **콜백 → 메인 페이지** 전환 직후 `cb.auth.getMe()` 가 401 으로 떨어져 비개발자 사용자가 영원히 로그인 루프에 갇히던 회귀를 3개 race + 1개 충돌 가드로 동시에 닫는다.
11
+
12
+ **(1) Race A — callback fire-and-forget cookie 부트스트랩**
13
+ `getCallbackResult()` / `exchangeCodeFromCallback()` 가 `setTokens` 직후 `bootstrapRefreshCookie()` 를 fire-and-forget 으로 호출했다. 표준 예제처럼 `window.location.href='/'` 가 즉시 발화하면 fetch 가 abort 되어 `cb_member_refresh_token` cookie 가 발급되지 않았다. 모바일·느린 네트워크에서 특히 잘 깨졌다.
14
+ - **변경**: `getCallbackResult()` 시그니처를 동기 → `Promise` 로 변경 (BREAKING — prelaunch 단계라 backward-compat 가드 생략). `exchangeCodeFromCallback()` / popup 핸들러도 cookie 부트스트랩을 *await* 한 뒤에 resolve. 호출자가 `await cb.oauth.getCallbackResult()` 후 navigation 하면 cookie 가 안전하게 저장된 상태가 보장된다.
15
+
16
+ **(2) Race B — 메인 페이지 entry race**
17
+ ConnectBase 인스턴스 생성 시 `tryRestoreSessionFromCookie()` 가 fire-and-forget 으로 진행되는데, 사용자 코드의 첫 인증 호출(`getMe()` 등) 이 그 promise 보다 먼저 발화하면 메모리 토큰 빈 채로 401 받았다.
18
+ - **변경**: 부팅 시 시작된 복구 promise 를 HttpClient 에 등록해, `prepareHeaders` 가 인증 호출 직전에 1회 await. 표준 예제 무수정으로 race 가 닫힌다.
19
+
20
+ **(3) 401 자동 복구**
21
+ 인증 호출이 401 받으면 cookie 기반 복구를 *한 번* 시도하고 retry. cookie 가 살아 있는 사용자의 첫 호출이 화면 401 으로 깨지지 않게 한다 (cookie 복구 실패 시엔 원본 401 그대로 throw — 무한 retry 차단).
22
+
23
+ **(4) Backend strict X-Public-Key dispatch (충돌 C)**
24
+ 같은 `.connectbase.world` 루트의 콘솔 admin refresh cookie 가 `credentials:'include'` 로 SDK 호출에 함께 첨부되어, SDK 가 콘솔 User JWT 를 silent reject 하는 케이스 차단. backend `/v1/auth/re-issue` 에서 cross-source cookie fallback 을 제거 — SDK 호출(X-Public-Key 있음) 은 `cb_member_refresh_token` 만, 콘솔 호출은 `refresh_token` 만 사용한다. 한쪽이 없으면 401 로 깔끔히 떨어뜨려 SDK 가드(`isConsolePlatformToken`) 가 더 이상 트리거되지 않는다.
25
+
26
+ ### Breaking
27
+
28
+ - `cb.oauth.getCallbackResult()` 가 `Promise` 를 반환합니다. 호출자는 반드시 `await` 해야 합니다. 컴파일 타임에 잡히지만, 동기 호출 코드는 콜백 결과를 `Promise<...>` 로 받아 `result.error` 가 항상 undefined 로 보입니다.
29
+ - **마이그레이션**: `const result = cb.oauth.getCallbackResult()` → `const result = await cb.oauth.getCallbackResult()`. React `useEffect` 안에서는 IIFE 패턴: `;(async () => { const result = await cb.oauth.getCallbackResult(); ... })()`.
30
+
31
+ ### Tests
32
+
33
+ - `test/http-race-recovery.test.ts` — race B / 401 auto-recovery / skipAuth 보존 5 케이스
34
+ - `test/oauth-callback-cookie-bootstrap.test.ts` — `await` 시그니처 + bootstrap-before-result 보장 추가
35
+
36
+ ## [3.22.1] - 2026-05-26
37
+
38
+ ### Documentation
39
+
40
+ README 에 3.22.0 에서 추가된 공개 API 3종을 정식 섹션으로 문서화. 코드 동작 변경 없음.
41
+
42
+ - **Key Types** 섹션에 `Server-side admin context` 추가 — `new ConnectBase({ publicKey, secretKey })` 가 admin 헤더(`X-Public-Key` + `Authorization: Bearer cb_sk_*`) 를 첨부하고 서버 `OptionalAdminSecretKey` 미들웨어가 RLS 를 우회한다는 점을 명시.
43
+ - **Authentication** 섹션에 `cb.auth.adminUpdateMember(memberID, fields)` 예제 + `secretKey` 미설정 시 throw / self-update 거절 동작 + `role` 이 RLS `auth.role` 의 backing field 임을 명시.
44
+ - **Server Functions** 섹션 신설 (Realtime 과 Endpoint 사이) — `cb.functions.invoke` + `cb.functions.getWebhookURL` + `http_trigger_auth` 3종(`none` / `public_key` / `secret_key`) + raw body / 헤더 forward / 10MB 한도 / Discord interactions 응답 예제.
45
+
6
46
  ## [3.22.0] - 2026-05-25
7
47
 
8
48
  ### Added — 서버사이드 admin (cb_sk_) 권한으로 data CRUD + 멤버 role 관리 (platform-issue 019e5a04, 019e59ca)
package/README.md CHANGED
@@ -37,6 +37,29 @@ Connect Base provides **two types** of Keys. Use the right key for your use case
37
37
 
38
38
  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
39
 
40
+ #### Server-side admin context (v3.22.0+)
41
+
42
+ When you create the SDK with **both** `publicKey` and `secretKey`, the client
43
+ attaches `X-Public-Key` (app identity) and `Authorization: Bearer cb_sk_*`
44
+ (privilege escalation) on every request. The server's `OptionalAdminSecretKey`
45
+ middleware verifies the secret key, sets an admin context, and **skips RLS**
46
+ for that request — useful for backend sync scripts, admin tools, and
47
+ `cb.auth.adminUpdateMember()`.
48
+
49
+ ```typescript
50
+ // SERVER-SIDE ONLY — never ship this in a browser/mobile bundle
51
+ const cb = new ConnectBase({
52
+ publicKey: process.env.CB_PUBLIC_KEY!, // cb_pk_
53
+ secretKey: process.env.CB_SECRET_KEY!, // cb_sk_ (admin)
54
+ })
55
+
56
+ // Bypasses RLS .write/.read rules
57
+ await cb.database.createData('orders', { ... })
58
+ ```
59
+
60
+ Without `secretKey`, every request is RLS-evaluated as normal — there is no
61
+ behavioral change for browser clients.
62
+
40
63
  ## Quick Start
41
64
 
42
65
  ```typescript
@@ -515,6 +538,36 @@ const result = await cb.oauth.signInWithPopup('google', 'https://myapp.com/oauth
515
538
  await cb.auth.signOut()
516
539
  ```
517
540
 
541
+ #### Admin: update another member (v3.22.0+)
542
+
543
+ Set another member's `nickname`, `role`, or `custom_data` from a server-side
544
+ admin context. Requires the SDK to be initialized with `secretKey` — calling
545
+ this without one throws synchronously. Self-update is rejected by the server.
546
+
547
+ ```typescript
548
+ // SERVER-SIDE ONLY — admin context required (publicKey + secretKey)
549
+ const cb = new ConnectBase({
550
+ publicKey: process.env.CB_PUBLIC_KEY!,
551
+ secretKey: process.env.CB_SECRET_KEY!,
552
+ })
553
+
554
+ // Grant role used by RLS `auth.role` expressions
555
+ await cb.auth.adminUpdateMember('019abc12-...', { role: 'editor' })
556
+
557
+ // Clear the role
558
+ await cb.auth.adminUpdateMember('019abc12-...', { role: '' })
559
+
560
+ // Multi-field update
561
+ await cb.auth.adminUpdateMember('019abc12-...', {
562
+ nickname: 'Alice',
563
+ role: 'admin',
564
+ custom_data: { level: 5 },
565
+ })
566
+ ```
567
+
568
+ `role` is the only way to populate the RLS expression variable `auth.role` —
569
+ end-users can't set it on themselves through the public profile API.
570
+
518
571
  ### Database
519
572
 
520
573
  ```typescript
@@ -956,6 +1009,56 @@ await session.stop()
956
1009
  | `promptTokens` | `number` | Input prompt tokens |
957
1010
  | `duration` | `number` | Generation time in ms |
958
1011
 
1012
+ ### Server Functions
1013
+
1014
+ Invoke a deployed function from the SDK, or expose it as a raw HTTP webhook
1015
+ that external services (Discord, Stripe, GitHub, Slack Events, etc.) can call
1016
+ directly.
1017
+
1018
+ ```typescript
1019
+ // Invoke a function (publicKey-authenticated; runs in your Knative pod)
1020
+ const result = await cb.functions.invoke('019abc12-...', { orderId: '...' })
1021
+ ```
1022
+
1023
+ #### Raw HTTP webhook URL (v3.22.0+)
1024
+
1025
+ For external SaaS webhooks where you can't customize the request shape (raw
1026
+ body, vendor-specific signature headers, arbitrary HTTP methods), enable
1027
+ `http_trigger_enabled` on the function (Console or MCP `update_function`) and
1028
+ register the URL returned by `getWebhookURL()` with the upstream service.
1029
+
1030
+ ```typescript
1031
+ const url = cb.functions.getWebhookURL('019abc12-...')
1032
+ // → https://api.connectbase.world/v1/public/functions/019abc12-.../webhook
1033
+ ```
1034
+
1035
+ | `http_trigger_auth` | Required header | Use for |
1036
+ |---|---|---|
1037
+ | `none` | _(none)_ | External SaaS webhooks (function verifies signature itself) |
1038
+ | `public_key` | `X-Public-Key: cb_pk_*` | Your own clients/services |
1039
+ | `secret_key` | `Authorization: Bearer cb_sk_*` | Server-to-server admin calls |
1040
+
1041
+ The endpoint forwards the **raw request body** (no JSON wrap), preserves
1042
+ method/path/query, and forwards all headers — so signature checks (Ed25519,
1043
+ HMAC-SHA256, Stripe-Signature, X-Hub-Signature-256) work end-to-end. Body
1044
+ limit is 10MB.
1045
+
1046
+ Return `{ statusCode, headers, body }` from the handler to emit a custom
1047
+ HTTP response (for example, Discord Interactions requires a `200` with a
1048
+ JSON body within 3 seconds):
1049
+
1050
+ ```javascript
1051
+ export async function handler(event, ctx) {
1052
+ // event.method / event.path / event.query / event.headers / event.body
1053
+ // are populated for webhook invocations.
1054
+ return {
1055
+ statusCode: 200,
1056
+ headers: { 'Content-Type': 'application/json' },
1057
+ body: JSON.stringify({ type: 1 }), // Discord PING ack
1058
+ }
1059
+ }
1060
+ ```
1061
+
959
1062
  ### Endpoint (Local Model Tunnel)
960
1063
 
961
1064
  `cb.endpoint.*` is a dumb pipe to your own GPU/model server running behind a