connectbase-client 3.23.0 → 3.25.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/README.md CHANGED
@@ -12,9 +12,9 @@ pnpm add connectbase-client
12
12
  yarn add connectbase-client
13
13
  ```
14
14
 
15
- ## Key Types
15
+ ## API Key Types
16
16
 
17
- Connect Base provides **two types** of Keys. Use the right key for your use case:
17
+ Connect Base provides **two types** of API Keys. Use the right key for your use case:
18
18
 
19
19
  | Type | Prefix | Use For | Permissions | Safe to Expose? |
20
20
  |------|--------|---------|-------------|-----------------|
@@ -26,7 +26,7 @@ Connect Base provides **two types** of Keys. Use the right key for your use case
26
26
  | Context | Key Type | Example |
27
27
  |---------|----------|---------|
28
28
  | Frontend SDK (`new ConnectBase()`) | **Public Key** (`cb_pk_`) | Web/app: DB queries, auth, file uploads |
29
- | `.env` file (`VITE_CONNECTBASE_PUBLIC_KEY`) | **Public Key** (`cb_pk_`) | React, Vue, etc. |
29
+ | `.env` file (`VITE_CONNECTBASE_API_KEY`) | **Public Key** (`cb_pk_`) | React, Vue, etc. |
30
30
  | CLI deploy (`.connectbaserc`) | **Public Key** (`cb_pk_`) | `npx connectbase 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 |
@@ -35,30 +35,7 @@ Connect Base provides **two types** of Keys. Use the right key for your use case
35
35
  >
36
36
  > ⚠️ **Never use Secret Keys in frontend code** — RLS is bypassed, exposing all data.
37
37
 
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
-
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.
38
+ Create API 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.
62
39
 
63
40
  ## Quick Start
64
41
 
@@ -67,7 +44,7 @@ import ConnectBase from 'connectbase-client'
67
44
 
68
45
  // Initialize the SDK — use a Public Key (cb_pk_)
69
46
  const cb = new ConnectBase({
70
- publicKey: 'cb_pk_your-public-key'
47
+ apiKey: 'cb_pk_your-public-key'
71
48
  })
72
49
 
73
50
  // Create a game room client
@@ -88,20 +65,8 @@ gameClient
88
65
  await gameClient.connect()
89
66
  const state = await gameClient.createRoom({
90
67
  maxPlayers: 4,
91
- tickRate: 64,
92
- scriptName: 'my-script', // Optional: attach a lua script (must be pre-uploaded + active)
68
+ tickRate: 64
93
69
  })
94
-
95
- // 3.14+ — Attached script 의 이름/버전을 검증하고 싶으면 createRoomDetailed 사용
96
- import { GameError } from 'connectbase-client'
97
- try {
98
- const r = await gameClient.createRoomDetailed({ scriptName: 'my-script' })
99
- console.log('attached', r.scriptName, 'v', r.scriptVersion)
100
- } catch (e) {
101
- if (e instanceof GameError && e.code === 'SCRIPT_NOT_FOUND') {
102
- console.error('script missing — candidates:', e.available)
103
- }
104
- }
105
70
  ```
106
71
 
107
72
  ## Features
@@ -114,9 +79,6 @@ try {
114
79
  - **WebRTC**: Real-time audio/video communication
115
80
  - **Payments**: Subscription and one-time payment support
116
81
  - **AI Streaming**: Real-time AI text generation via WebSocket (Gemini)
117
- - **Knowledge Base (RAG)**: Document indexing + BM25 search with nori 한국어 형태소. PDF / DOCX / text file upload via `addDocumentFromFile`
118
- - **Endpoint**: Call your own GPU models on your own PC through one `cb_pk_*` key — ConnectBase forwards the payload as-is (dumb pipe)
119
- - **Support**: End-user feedback/issue reporting — users send issues to app operators, AI auto-classifies summary/urgency/category
120
82
  - **CLI**: Command-line tool for deploying web storage and tunneling local services
121
83
 
122
84
  ## CLI
@@ -127,14 +89,14 @@ Deploy your web application to Connect Base Web Storage with a single command.
127
89
 
128
90
  ```bash
129
91
  # 1. Initialize (one-time setup)
130
- npx connectbase init
92
+ npx connectbase-client init
131
93
 
132
94
  # 2. Deploy
133
95
  npm run deploy
134
96
  ```
135
97
 
136
98
  The `init` command will:
137
- - Ask for your Public Key
99
+ - Ask for your API Key
138
100
  - List existing web storages or create a new one automatically
139
101
  - Create a `.connectbaserc` config file
140
102
  - Add `.connectbaserc` to `.gitignore`
@@ -153,7 +115,7 @@ The `init` command will:
153
115
  If you prefer not to use `init`, you can pass options directly:
154
116
 
155
117
  ```bash
156
- npx connectbase deploy ./dist -s <storage-id> -k <public-key>
118
+ npx connectbase-client deploy ./dist -s <storage-id> -k <api-key>
157
119
  ```
158
120
 
159
121
  ### Options
@@ -161,12 +123,10 @@ npx connectbase deploy ./dist -s <storage-id> -k <public-key>
161
123
  | Option | Alias | Description |
162
124
  |--------|-------|-------------|
163
125
  | `--storage <id>` | `-s` | Storage ID |
164
- | `--public-key <key>` | `-k` | API Key |
126
+ | `--api-key <key>` | `-k` | API Key |
165
127
  | `--base-url <url>` | `-u` | Custom server URL |
166
128
  | `--timeout <sec>` | `-t` | Tunnel request timeout in seconds (tunnel only) |
167
129
  | `--max-body <MB>` | | Tunnel max body size in MB (tunnel only) |
168
- | `--label <name>` | | Auto-register the issued tunnel as an endpoint binding (tunnel only). Requires Secret Key. SDK callers can then use `cb.endpoint.call(label, …)` |
169
- | `--description <text>` | | Endpoint binding description (only valid with `--label`) |
170
130
  | `--help` | `-h` | Show help |
171
131
  | `--version` | `-v` | Show version |
172
132
 
@@ -176,14 +136,14 @@ Expose a local server to the internet through a secure WebSocket tunnel. Useful
176
136
 
177
137
  ```bash
178
138
  # Expose local port 8084 to the internet
179
- npx connectbase tunnel 8084 -k <public-key>
139
+ npx connectbase-client tunnel 8084 -k <api-key>
180
140
 
181
141
  # With environment variable
182
- export CONNECTBASE_PUBLIC_KEY=your-public-key
183
- npx connectbase tunnel 8084
142
+ export CONNECTBASE_API_KEY=your-api-key
143
+ npx connectbase-client tunnel 8084
184
144
 
185
145
  # For GPU servers or long-running tasks (e.g., image generation)
186
- npx connectbase tunnel 7860 --timeout 300 --max-body 50
146
+ npx connectbase-client tunnel 7860 --timeout 300 --max-body 50
187
147
  ```
188
148
 
189
149
  The tunnel creates a public URL like `https://tunnel.connectbase.world/<tunnel-id>/` that proxies all HTTP requests to your local service.
@@ -204,30 +164,13 @@ Features:
204
164
  - Graceful shutdown with Ctrl+C
205
165
  - No external dependencies (uses Node.js built-in modules)
206
166
 
207
- #### Auto-register an endpoint binding (`--label`)
208
-
209
- For workflows where you want the SDK to call your local model by a stable name
210
- (`cb.endpoint.call("comfyui-main", …)`) instead of a random tunnel URL, pass
211
- `--label <name>`. The CLI registers the issued `tunnel_id` as an endpoint binding
212
- on the server, so your SDK only needs the Public Key.
213
-
214
- ```bash
215
- # Start ComfyUI on port 8188, expose it as endpoint label "comfyui-main"
216
- npx connectbase tunnel 8188 --label comfyui-main --description "ComfyUI on my desktop"
217
- ```
218
-
219
- Authentication uses your User Secret Key (`cb_sk_*`); the CLI calls the dual-auth
220
- route `POST /v1/apps/:appID/endpoints/cli`. If the label already exists, the CLI
221
- warns and keeps the tunnel running — update the binding to the new `tunnel_id`
222
- from the console if needed.
223
-
224
167
  ### Configuration File
225
168
 
226
169
  The `init` command creates `.connectbaserc` automatically. You can also create it manually:
227
170
 
228
171
  ```json
229
172
  {
230
- "publicKey": "your-public-key",
173
+ "apiKey": "your-api-key",
231
174
  "storageId": "your-storage-id",
232
175
  "deployDir": "./dist"
233
176
  }
@@ -236,9 +179,9 @@ The `init` command creates `.connectbaserc` automatically. You can also create i
236
179
  ### Environment Variables
237
180
 
238
181
  ```bash
239
- export CONNECTBASE_PUBLIC_KEY=your-public-key
182
+ export CONNECTBASE_API_KEY=your-api-key
240
183
  export CONNECTBASE_STORAGE_ID=your-storage-id
241
- npx connectbase deploy ./dist
184
+ npx connectbase-client deploy ./dist
242
185
  ```
243
186
 
244
187
  ### Requirements
@@ -266,43 +209,6 @@ curl -X PUT "https://api.connectbase.world/v1/apps/{appID}/storages/webs/{storag
266
209
 
267
210
  ### Game Server
268
211
 
269
- #### `cb.game.config` — Feature Opt-in (v3.1+, SDK 3.3.0+)
270
-
271
- The game server's 7 features (`matchqueue` / `leaderboard` / `entity` / `scripts` /
272
- `voice` / `replay` / `spectator`) are **explicit opt-in per app** as of v3.1
273
- (2026-04-30). Disabled features return HTTP **403 `feature_disabled`**.
274
- New apps default to all-OFF; existing apps without a config row fall back to
275
- all-ON for compatibility.
276
-
277
- ```typescript
278
- // Inspect current toggles
279
- const cfg = await cb.game.config.get(appId)
280
- // → { matchqueue_enabled, leaderboard_enabled, entity_enabled, scripts_enabled,
281
- // voice_enabled, replay_enabled, spectator_enabled }
282
-
283
- // Partial update — only the keys you send are applied; others are preserved.
284
- await cb.game.config.set(appId, {
285
- matchqueue_enabled: true,
286
- leaderboard_enabled: true,
287
- })
288
-
289
- // Single-toggle convenience wrappers
290
- await cb.game.config.enable(appId, 'matchqueue_enabled')
291
- await cb.game.config.disable(appId, 'voice_enabled')
292
- ```
293
-
294
- The PATCH publishes a NATS invalidation so game-server caches drop the entry
295
- immediately (30s TTL is the safety net).
296
-
297
- | HTTP code | Body `error` | Meaning | Client action |
298
- |-----------|--------------|---------|---------------|
299
- | **403** | `feature_disabled` | Feature is not enabled for this app | Toggle via console or `cb.game.config.set(...)` |
300
- | **429** | `cap_exceeded` | Per-app cardinality cap reached | Remove old rows or ask the operator to raise the cap env |
301
- | **402** | `quota_exceeded` | Plan limit reached on a write route | Upgrade plan |
302
-
303
- See [docs/game-server/OPT_IN.md](https://github.com/connectbase-world/connectbase/blob/release/docs/game-server/OPT_IN.md)
304
- for the full policy.
305
-
306
212
  #### GameRoom
307
213
 
308
214
  The main class for real-time game communication.
@@ -357,11 +263,8 @@ Create a new game room.
357
263
  const state = await gameClient.createRoom({
358
264
  roomId: 'my-custom-room', // Optional: Custom room ID
359
265
  categoryId: 'battle-royale', // Optional: Room category
360
- maxPlayers: 100, // Optional: Max players (default: 100)
266
+ maxPlayers: 100, // Optional: Max players (default: 10)
361
267
  tickRate: 64, // Optional: Server tick rate (default: 64)
362
- scriptName: 'main', // Optional (3.11.0+): Lua script attached to the room
363
- // (uploaded+activated via console or POST /v1/game/:appID/scripts).
364
- // Required for onTick / onPlayerJoin / onAction etc. to fire.
365
268
  metadata: { map: 'forest' } // Optional: Custom metadata
366
269
  })
367
270
  ```
@@ -463,12 +366,6 @@ gameClient
463
366
  .on('onChat', (message: ChatMessage) => {
464
367
  // Called when a chat message is received
465
368
  })
466
- .on('onMessage', (msg) => {
467
- // Called for custom broadcast messages from the server-side Lua script
468
- // (room.broadcast / room.send_to). Standard types (delta/chat/...) go to
469
- // their dedicated handlers; only unknown `type` messages arrive here.
470
- // Branch on msg.type for game-specific protocols.
471
- })
472
369
  .on('onError', (error: ErrorMessage) => {
473
370
  // Called on errors
474
371
  })
@@ -538,41 +435,11 @@ const result = await cb.oauth.signInWithPopup('google', 'https://myapp.com/oauth
538
435
  await cb.auth.signOut()
539
436
  ```
540
437
 
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
-
571
438
  ### Database
572
439
 
573
440
  ```typescript
574
441
  // Query data
575
- const { data, total_count } = await cb.database.getData('table-id', {
442
+ const { data } = await cb.database.getData('table-id', {
576
443
  where: { status: 'active' },
577
444
  limit: 10
578
445
  })
@@ -589,20 +456,13 @@ const { data } = await cb.database.getData('table-id', {
589
456
  limit: 20
590
457
  })
591
458
 
592
- // Insert data — returns the created DataItem (id + data + created_at + updated_at)
459
+ // Insert data
593
460
  const newItem = await cb.database.createData('table-id', {
594
461
  data: { name: 'John', email: 'john@example.com' }
595
462
  })
596
- console.log(newItem.id) // use immediately for navigation / cache updates
597
463
 
598
- // Bulk insert — returns { created: DataItem[], total, success, failed? }
599
- const bulk = await cb.database.createMany('table-id', [
600
- { data: { name: 'User1' } },
601
- { data: { name: 'User2' } }
602
- ])
603
-
604
- // Update data — returns the updated DataItem with merged fields
605
- const updated = await cb.database.updateData('table-id', 'data-id', {
464
+ // Update data
465
+ await cb.database.updateData('table-id', 'data-id', {
606
466
  data: { name: 'Jane' }
607
467
  })
608
468
 
@@ -659,41 +519,26 @@ nearby.results.forEach(place => {
659
519
 
660
520
  #### Batch & Transactions
661
521
 
662
- `table_id` 는 항상 UUID. 콘솔/REST 로 생성한 테이블의 UUID 를 그대로 사용한다.
663
-
664
- v3.12+ 부터 server 가 부분 실패(`success: false`)를 응답하면 SDK 가 첫 실패 op 의
665
- error 메시지로 throw 한다 — silent success 회귀 방지 차원. 호출자는 try/catch 로 감싼다.
666
-
667
522
  ```typescript
668
523
  // Batch: atomic multi-table operations
669
- try {
670
- const result = await cb.database.batch([
671
- { type: 'create', table_id: ORDERS_TABLE_ID, data: { product: 'A', qty: 1 } },
672
- { type: 'update', table_id: INVENTORY_TABLE_ID, doc_id: 'item-a', operators: {
673
- qty: { type: 'increment', value: -1 }
674
- }},
675
- { type: 'update', table_id: STATS_TABLE_ID, doc_id: 'daily', operators: {
676
- order_count: { type: 'increment', value: 1 },
677
- last_order: { type: 'serverTimestamp' }
678
- }}
679
- ])
680
- // result.success, result.results[i].{success, doc_id, error}
681
- } catch (e) {
682
- // RLS 거부 / 검증 실패 / table_id 오타 등 — 전체 batch 가 atomic 하게 롤백
683
- console.error('batch failed:', (e as Error).message)
684
- }
524
+ await cb.database.batch([
525
+ { type: 'create', table: 'orders', data: { product: 'A', qty: 1 } },
526
+ { type: 'update', table: 'inventory', doc_id: 'item-a', operators: {
527
+ qty: { type: 'increment', value: -1 }
528
+ }},
529
+ { type: 'update', table: 'stats', doc_id: 'daily', operators: {
530
+ order_count: { type: 'increment', value: 1 },
531
+ last_order: { type: 'serverTimestamp' }
532
+ }}
533
+ ])
685
534
 
686
535
  // Transaction: read-then-write with ACID guarantees
687
- try {
688
- await cb.database.transaction(
689
- [{ table_id: ACCOUNTS_TABLE_ID, doc_id: 'user-1', alias: 'sender' }],
690
- [{ type: 'update', table_id: ACCOUNTS_TABLE_ID, doc_id: 'user-1', operators: {
691
- balance: { type: 'increment', value: -100 }
692
- }}]
693
- )
694
- } catch (e) {
695
- console.error('transaction failed:', (e as Error).message)
696
- }
536
+ await cb.database.transaction(
537
+ [{ table: 'accounts', doc_id: 'user-1', alias: 'sender' }],
538
+ [{ type: 'update', table: 'accounts', doc_id: 'user-1', operators: {
539
+ balance: { type: 'increment', value: -100 }
540
+ }}]
541
+ )
697
542
  ```
698
543
 
699
544
  #### Populate (Relation Query / JOIN)
@@ -832,76 +677,6 @@ const { pages } = await cb.storage.listPageMetas('web-storage-id')
832
677
  await cb.storage.deletePageMeta('web-storage-id', '/products/123')
833
678
  ```
834
679
 
835
- ### Knowledge Base (RAG)
836
-
837
- 문서를 등록하고 BM25 풀텍스트 검색으로 관련 청크를 찾는 RAG 인프라. 한국어는 nori 형태소 분석기 적용. AI 채팅에 컨텍스트로 연결하려면 `cb.ai.chatStream({ knowledgeBaseId })` 를 사용한다.
838
-
839
- ```typescript
840
- // 텍스트 문서 추가 (즉시 처리)
841
- const doc = await cb.knowledge.addDocument('kb-id', {
842
- name: '환불 정책',
843
- source_type: 'text',
844
- content: '환불은 구매 후 7일 이내 신청 가능합니다...',
845
- metadata: { category: 'policy' }
846
- })
847
- // doc.status: 'pending' → 백그라운드 처리 후 'ready'
848
-
849
- // URL 에서 가져오기
850
- await cb.knowledge.addDocument('kb-id', {
851
- name: '도움말',
852
- source_type: 'url',
853
- source_url: 'https://example.com/help.html',
854
- })
855
-
856
- // PDF / DOCX / text 파일 업로드 (3.17.0+)
857
- // 브라우저: <input type="file"> 결과를 그대로 전달
858
- const file = (document.querySelector('input[type=file]') as HTMLInputElement).files![0]
859
- await cb.knowledge.addDocumentFromFile('kb-id', file, {
860
- metadata: { tag: 'manual' },
861
- })
862
-
863
- // Node.js: fs.readFileSync 로 읽은 Buffer
864
- import { readFileSync } from 'node:fs'
865
- await cb.knowledge.addDocumentFromFile('kb-id', {
866
- data: readFileSync('./report.pdf'),
867
- mimeType: 'application/pdf',
868
- name: 'report.pdf',
869
- })
870
-
871
- // 문서 목록 / 삭제
872
- const { documents } = await cb.knowledge.listDocuments('kb-id')
873
- await cb.knowledge.deleteDocument('kb-id', 'doc-id')
874
-
875
- // 문서 수정 — content/file_content/metadata 변경 시 전체 재색인, name 만 보내면 라벨만 변경
876
- await cb.knowledge.updateDocument('kb-id', 'doc-id', {
877
- content: '환불은 구매 후 14일 이내에 가능합니다...',
878
- })
879
-
880
- // 키워드 검색 (BM25)
881
- const results = await cb.knowledge.search('kb-id', {
882
- query: '환불 정책이 어떻게 되나요?',
883
- top_k: 5,
884
- })
885
- results.results.forEach(r => console.log(`[${r.score.toFixed(2)}] ${r.document_name}: ${r.content.slice(0, 80)}...`))
886
-
887
- // Agentic Search — AI 가 다중 쿼리 자동 생성 (앱에 AI 프로바이더 설정 필요)
888
- await cb.knowledge.search('kb-id', { query: '회원 등급별 혜택 비교', agentic: true })
889
-
890
- // GET 방식 (간단한 사용)
891
- await cb.knowledge.searchGet('kb-id', '환불', 3)
892
- ```
893
-
894
- **파일 업로드 제약 (`addDocumentFromFile`)**
895
-
896
- - 지원 MIME: `application/pdf` (텍스트 PDF), DOCX, `text/*` (plain/markdown/csv/html), `application/json`
897
- - 미지원: 스캔 이미지 PDF / OCR / HWP / XLSX → `unsupported mime type for text extraction` 에러
898
- - 크기 상한: **50MB** (원본 바이너리 기준)
899
- - 추출 결과 빈 텍스트일 경우 `extracted text is empty` 에러 (스캔 PDF 등)
900
-
901
- **사용자별 격리 (다중 사용자 RAG)**
902
-
903
- `Authorization: Bearer <appmember-jwt>` 를 함께 보내면 서버가 자동으로 본인 metadata.user_id 로 결과를 제한하고, addDocument 시에도 자동 태깅. `search` 의 `where` 에 `'$auth.member_id'` 토큰 사용 시 서버가 인증된 AppMember ID 로 치환한다.
904
-
905
680
  ### Realtime
906
681
 
907
682
  ```typescript
@@ -926,29 +701,6 @@ await subscription.unsubscribe()
926
701
  await cb.realtime.disconnect()
927
702
  ```
928
703
 
929
- #### Presence / Typing
930
-
931
- Presence(온라인 상태) 와 typing(입력 중 표시) 은 `cb.realtime.*` 가 단일 SoT 입니다.
932
- v2.0.0 에서 `cb.database.setPresence` / `subscribePresence` 는 제거되었습니다.
933
- v1.x 에서 마이그레이션은 [MIGRATION-v2.md](./MIGRATION-v2.md) 참고.
934
-
935
- ```typescript
936
- // 본인 온라인 상태 publish
937
- await cb.realtime.setPresence('online', { device: 'web', metadata: { nickname: '홍길동' } })
938
-
939
- // 다른 사용자 상태 구독
940
- const unsub = await cb.realtime.subscribePresence('user-id', (info) => {
941
- console.log(info.userId, info.status, info.eventType) // join | leave | update
942
- })
943
-
944
- // 룸 단위 typing indicator
945
- await cb.realtime.startTyping('room-id')
946
- await cb.realtime.stopTyping('room-id')
947
- const unsubTyping = await cb.realtime.onTypingChange('room-id', (typing) => {
948
- console.log(typing.users) // 현재 입력 중인 사용자 ID 목록
949
- })
950
- ```
951
-
952
704
  #### AI Streaming
953
705
 
954
706
  Real-time AI text generation using Gemini API through WebSocket.
@@ -1009,241 +761,26 @@ await session.stop()
1009
761
  | `promptTokens` | `number` | Input prompt tokens |
1010
762
  | `duration` | `number` | Generation time in ms |
1011
763
 
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
-
1062
- ### Endpoint (Local Model Tunnel)
1063
-
1064
- `cb.endpoint.*` is a dumb pipe to your own GPU/model server running behind a
1065
- ConnectBase tunnel. ConnectBase doesn't know your model, payload, or response
1066
- shape — it routes a `cb_pk_*` call by label to the registered tunnel and forwards
1067
- the body and headers as-is.
1068
-
1069
- **Setup**: run `connectbase tunnel <port> --label <name>` once on the machine
1070
- hosting the model (see [Tunnel](#tunnel)) — that registers the binding. Then any
1071
- client with the app's Public Key can call it.
1072
-
1073
- #### `cb.endpoint.call(label, init): Promise<Response>`
1074
-
1075
- `fetch()`-compatible signature. Returns the raw `Response` — read the body as
1076
- JSON, text, or stream as needed.
1077
-
1078
- ```typescript
1079
- const cb = new ConnectBase({ publicKey: 'cb_pk_...' })
1080
-
1081
- // ComfyUI prompt graph
1082
- const res = await cb.endpoint.call('comfyui-main', {
1083
- method: 'POST',
1084
- path: '/prompt',
1085
- headers: { 'Content-Type': 'application/json' },
1086
- body: JSON.stringify({ prompt: { /* ComfyUI node graph */ } }),
1087
- })
1088
- const data = await res.json()
1089
- ```
1090
-
1091
- ```typescript
1092
- // Streaming response (SSE / chunked) — vLLM chat completions
1093
- const res = await cb.endpoint.call('vllm-local', {
1094
- method: 'POST',
1095
- path: '/v1/chat/completions',
1096
- headers: { 'Content-Type': 'application/json' },
1097
- body: JSON.stringify({ stream: true, messages: [/* { role, content } */] }),
1098
- })
1099
- if (!res.body) throw new Error('no stream')
1100
- const reader = res.body.getReader()
1101
- while (true) {
1102
- const { done, value } = await reader.read()
1103
- if (done) break
1104
- // value is a Uint8Array — decode and process chunk
1105
- }
1106
- ```
1107
-
1108
- ```typescript
1109
- // Cancel an in-flight request
1110
- const ctrl = new AbortController()
1111
- setTimeout(() => ctrl.abort(), 30_000)
1112
- await cb.endpoint.call('hunyuan-laptop', {
1113
- method: 'POST',
1114
- path: '/generate',
1115
- signal: ctrl.signal,
1116
- body: JSON.stringify({ /* model input */ }),
1117
- })
1118
- ```
1119
-
1120
- **`EndpointCallInit`**
1121
-
1122
- | Field | Type | Required | Description |
1123
- |-------|------|----------|-------------|
1124
- | `path` | `string` | yes | Path on your model server, must start with `/` (e.g. `/prompt`, `/v1/chat/completions`) |
1125
- | `method` | `string` | no (`GET`) | HTTP method |
1126
- | `headers` | `HeadersInit` | no | Extra request headers; `X-Public-Key` is auto-injected unless you set it |
1127
- | `body` | `BodyInit \| null` | no | Request body — `string`, `Blob`, `ArrayBuffer`, `FormData`, or `ReadableStream` |
1128
- | `signal` | `AbortSignal` | no | Abort signal for cancellation |
1129
-
1130
- The SDK assembles the URL as `${baseUrl}/v1/proxy/${label}${path}` and forwards
1131
- the request. Because the response is the raw `fetch` `Response`, streaming
1132
- formats (SSE, chunked, NDJSON) work out of the box.
1133
-
1134
- #### `cb.endpoint.pollUntil<T>(label, init, predicate, opts?): Promise<T>`
1135
-
1136
- One-line "submit job → poll for result" pattern. Repeatedly calls the same
1137
- endpoint until `predicate` returns a value. Designed for ComfyUI `/history/{id}`,
1138
- A1111 `/sdapi/v1/progress`, or any custom queue API.
1139
-
1140
- Behavior:
1141
- - Calls `cb.endpoint.call(label, init)` and passes the parsed body to `predicate`
1142
- - Returns `undefined` from `predicate` → wait `intervalMs` and retry
1143
- - Returns a value from `predicate` → resolve immediately with that value
1144
- - HTTP `5xx` / network error → retry. HTTP `4xx` → reject (job-level error)
1145
- - `timeoutMs` exceeded or `signal` aborted → reject
1146
-
1147
- ```typescript
1148
- type Hist = Record<
1149
- string,
1150
- { outputs: Record<string, { images?: { filename: string }[] }> }
1151
- >
1152
-
1153
- const filename = await cb.endpoint.pollUntil<string>(
1154
- 'comfyui-main',
1155
- { path: `/history/${promptId}` },
1156
- (data: Hist) => {
1157
- const entry = data[promptId]
1158
- if (!entry) return undefined // still queued
1159
- for (const out of Object.values(entry.outputs)) {
1160
- const img = out.images?.[0]
1161
- if (img) return img.filename
1162
- }
1163
- return undefined
1164
- },
1165
- { intervalMs: 1000, timeoutMs: 5 * 60_000 },
1166
- )
1167
- ```
1168
-
1169
- **`PollUntilOptions`**
1170
-
1171
- | Field | Type | Default | Description |
1172
- |-------|------|---------|-------------|
1173
- | `intervalMs` | `number` | `1500` | Poll interval in ms |
1174
- | `timeoutMs` | `number` | `300000` (5 min) | Total timeout in ms — reject if exceeded |
1175
- | `parse` | `'json' \| 'text' \| 'none'` | `'json'` | Body parser. `'json'` falls back to `undefined` on parse error |
1176
- | `signal` | `AbortSignal` | — | External cancel signal — reject immediately on abort |
1177
-
1178
- #### `cb.endpoint.url(label, path): string`
1179
-
1180
- Returns the assembled call URL (`${baseUrl}/v1/proxy/${label}${path}`) for
1181
- URL-passing scenarios where you control the request and can attach the
1182
- `X-Public-Key` header yourself.
1183
-
1184
- ⚠️ **Browser-native APIs that cannot set custom headers will fail with `401`.**
1185
- ConnectBase's proxy requires `X-Public-Key` on every call (header-only — no
1186
- `?api_key=` fallback), so `<img src>`, `new Image()`, native `WebSocket`,
1187
- `<script src>`, EventSource, etc. **cannot authenticate** through this URL.
1188
- Use `cb.endpoint.call()` instead for those cases:
1189
-
1190
- ```typescript
1191
- // ✅ Render an image: download via call(), then convert to a blob URL
1192
- const res = await cb.endpoint.call('comfyui-main', {
1193
- path: `/view?filename=${encodeURIComponent(name)}`,
1194
- })
1195
- img.src = URL.createObjectURL(await res.blob())
1196
- // ...later: URL.revokeObjectURL(img.src)
1197
- ```
1198
-
1199
- For permanent images (works across CDN, survives tunnel restarts), upload the
1200
- blob to `cb.storage` and use `saved.url` — see
1201
- [`examples/ai-image-generator/`](https://github.com/connectbase-world/connectbase/tree/release/examples/ai-image-generator).
1202
-
1203
- When `cb.endpoint.url()` IS the right tool:
1204
-
1205
- - Logging / debugging the resolved tunnel URL
1206
- - Passing the URL to a backend service or worker that will make the call with proper headers
1207
- - Building a `RequestInfo` for a custom `fetch()` wrapper (you control headers)
1208
-
1209
- ```typescript
1210
- console.log(cb.endpoint.url('comfyui-main', '/prompt'))
1211
- // → https://api.connectbase.world/v1/proxy/comfyui-main/prompt
1212
-
1213
- // Hand the URL to a Service Worker that injects X-Public-Key
1214
- sw.postMessage({ url: cb.endpoint.url('comfyui-main', '/prompt'), key: PK })
1215
- ```
1216
-
1217
764
  ### Push Notifications
1218
765
 
1219
766
  ```typescript
1220
- // Register a device (FCM for Android, APNS for iOS)
1221
- const device = await cb.push.registerDevice({
1222
- device_token: 'fcm-token-or-apns-token',
1223
- platform: 'android', // 'android' | 'ios' | 'web'
1224
- device_name: 'Galaxy S24'
767
+ // Register for push notifications
768
+ await cb.push.register({
769
+ token: 'fcm-token-or-apns-token',
770
+ platform: 'android' // or 'ios', 'web'
1225
771
  })
1226
772
 
1227
- // Subscribe the device to a topic (deviceToken is required)
1228
- await cb.push.subscribeTopic(device.device_token, 'news')
1229
-
1230
- // Unsubscribe the device from a topic
1231
- await cb.push.unsubscribeTopic(device.device_token, 'news')
773
+ // Subscribe to topics
774
+ await cb.push.subscribeToTopic('news')
1232
775
 
1233
- // Web Push (browsers)
1234
- const vapidKey = await cb.push.getVAPIDPublicKey()
1235
- const registration = await navigator.serviceWorker.ready
1236
- const subscription = await registration.pushManager.subscribe({
1237
- userVisibleOnly: true,
1238
- applicationServerKey: vapidKey.public_key
1239
- })
1240
- await cb.push.registerWebPush(subscription)
776
+ // Unsubscribe from topic
777
+ await cb.push.unsubscribeFromTopic('news')
1241
778
  ```
1242
779
 
1243
780
  ### WebRTC
1244
781
 
1245
782
  ```typescript
1246
- // Public Key/JWT 유효성 사전 검증
783
+ // API Key/JWT 유효성 사전 검증
1247
784
  const result = await cb.webrtc.validate()
1248
785
  if (result.valid) {
1249
786
  console.log('인증 성공:', result.app_id)
@@ -1284,33 +821,6 @@ const status = await cb.subscription.getStatus()
1284
821
  await cb.subscription.cancel()
1285
822
  ```
1286
823
 
1287
- ### Support (End-user Issue Reporting)
1288
-
1289
- End-user 가 앱 운영자에게 직접 버그·질문·요청을 발행하는 채널. 운영자 콘솔의 inbox 에 들어가며, AI 가 자동으로 요약·긴급도·카테고리를 분류한다 (운영자가 AI config 등록 시).
1290
-
1291
- ```typescript
1292
- // 로그인 사용자 (AppMember JWT 자동 첨부)
1293
- await cb.support.reportIssue({
1294
- title: "결제 화면이 멈춰요",
1295
- body: "결제 버튼 클릭 후 로딩이 끝나지 않습니다.",
1296
- category: "bug", // bug | question | feature_request | incident | other
1297
- metadata: { pageUrl: window.location.href }
1298
- })
1299
-
1300
- // 익명 발행 + reCAPTCHA v3 (운영자가 RECAPTCHA_SECRET 설정한 경우 권장)
1301
- const recaptchaToken = await grecaptcha.execute(SITE_KEY, { action: 'report_issue' })
1302
- await cb.support.reportIssue({
1303
- title: "...",
1304
- body: "...",
1305
- anonymousEmail: "user@example.com", // 회신 받을 이메일 (선택)
1306
- recaptchaToken,
1307
- })
1308
- ```
1309
-
1310
- 응답: `{ id, status: 'open', created_at }` (보안상 최소 정보만).
1311
-
1312
- 발행자가 결과를 조회하는 채널은 후속 plan 에서 추가될 예정 — 현재는 운영자가 외부 webhook(이메일/Slack 등)으로 회신하는 방식 권장.
1313
-
1314
824
  ## Types
1315
825
 
1316
826
  ### GameState
@@ -1365,53 +875,20 @@ interface ConnectionState {
1365
875
  ## Error Handling
1366
876
 
1367
877
  ```typescript
1368
- import ConnectBase, { ApiError, AuthError } from 'connectbase-client'
1369
-
1370
878
  try {
1371
- await cb.auth.signInMember({ login_id, password })
879
+ await gameClient.connect()
1372
880
  } catch (error) {
1373
- if (error instanceof ApiError) {
1374
- // HTTP 응답 기반 에러: status/code/details 로 분기 가능
1375
- if (error.statusCode === 429) {
1376
- const details = error.details as { retry_after_seconds?: number } | undefined
1377
- const retryAfter = details?.retry_after_seconds
1378
- // ...
1379
- }
1380
- } else if (error instanceof AuthError) {
1381
- // refresh 실패/토큰 만료
881
+ if (error instanceof Error) {
882
+ console.error('Connection failed:', error.message)
1382
883
  }
1383
884
  }
1384
885
 
1385
- // Game API 별도 이벤트 핸들러도 지원
886
+ // Or use event handlers
1386
887
  gameClient.on('onError', (error) => {
1387
888
  console.error('Game error:', error.message)
1388
889
  })
1389
890
  ```
1390
891
 
1391
- ### 전역 에러 관찰자 (v1.9.0+)
1392
-
1393
- `ConnectBase` 초기화 시 `onError` 옵션을 주면 모든 `ApiError` / `AuthError` 가 한 곳으로 모입니다. Sentry/Datadog 등 관측성 툴과 연결하기 쉽습니다.
1394
-
1395
- ```typescript
1396
- const cb = new ConnectBase({
1397
- publicKey: 'cb_pk_...',
1398
- onError: (error) => {
1399
- Sentry.captureException(error)
1400
- },
1401
- })
1402
- ```
1403
-
1404
- ### 요청 타임아웃 (v1.9.0+)
1405
-
1406
- 기본 30초 타임아웃이 모든 HTTP 호출에 적용됩니다. `requestTimeoutMs` 로 전역 기본값을 바꾸거나, 0 이하 값을 주면 비활성화할 수 있습니다.
1407
-
1408
- ```typescript
1409
- const cb = new ConnectBase({
1410
- publicKey: 'cb_pk_...',
1411
- requestTimeoutMs: 60000, // 60s
1412
- })
1413
- ```
1414
-
1415
892
  ## Best Practices
1416
893
 
1417
894
  ### State Synchronization
@@ -1462,7 +939,7 @@ setInterval(async () => {
1462
939
  ```typescript
1463
940
  import ConnectBase from 'connectbase-client'
1464
941
 
1465
- const cb = new ConnectBase({ publicKey: 'your-public-key' })
942
+ const cb = new ConnectBase({ apiKey: 'your-api-key' })
1466
943
  const game = cb.game.createClient({ clientId: `player-${Date.now()}` })
1467
944
 
1468
945
  // Local player state