connectbase-client 3.24.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 +52 -575
- package/dist/cli.js +353 -1748
- package/dist/connect-base.umd.js +2 -4
- package/dist/index.d.mts +361 -3254
- package/dist/index.d.ts +361 -3254
- package/dist/index.js +1081 -4163
- package/dist/index.mjs +1078 -4155
- package/package.json +7 -19
- package/CHANGELOG.md +0 -1884
- package/LICENSE +0 -21
- package/MIGRATION-v2.md +0 -149
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 (`
|
|
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
|
-
|
|
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
|
|
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 <
|
|
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
|
-
| `--
|
|
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 <
|
|
139
|
+
npx connectbase-client tunnel 8084 -k <api-key>
|
|
180
140
|
|
|
181
141
|
# With environment variable
|
|
182
|
-
export
|
|
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
|
-
"
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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
|
-
//
|
|
599
|
-
|
|
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
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
{ type: '
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
{ type: '
|
|
676
|
-
|
|
677
|
-
|
|
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
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
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
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
platform: 'android'
|
|
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
|
|
1228
|
-
await cb.push.
|
|
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
|
-
//
|
|
1234
|
-
|
|
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
|
-
//
|
|
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
|
|
879
|
+
await gameClient.connect()
|
|
1372
880
|
} catch (error) {
|
|
1373
|
-
if (error instanceof
|
|
1374
|
-
|
|
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
|
-
//
|
|
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({
|
|
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
|