npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

connectbase-client

v0.6.38

Published

Connect Base JavaScript/TypeScript SDK for browser and Node.js

Downloads

3,494

Readme

connectbase-client

Connect Base JavaScript/TypeScript SDK for building real-time multiplayer games and applications.

Installation

npm install connectbase-client
# or
pnpm add connectbase-client
# or
yarn add connectbase-client

API Key Types

Connect Base provides two types of API Keys. Use the right key for your use case:

| Type | Prefix | Use For | Permissions | Safe to Expose? | |------|--------|---------|-------------|-----------------| | Public Key | cb_pk_ | SDK / Web apps | Limited (RLS enforced) | ✅ Yes — safe in frontend code | | Secret Key | cb_sk_ | MCP / Admin tools | Full access (bypasses RLS) | ❌ Never expose in frontend or public repos |

Which key should I use?

| Context | Key Type | Example | |---------|----------|---------| | Frontend SDK (new ConnectBase()) | Public Key (cb_pk_) | Web/app: DB queries, auth, file uploads | | .env file (VITE_CONNECTBASE_API_KEY) | Public Key (cb_pk_) | React, Vue, etc. | | CLI deploy (.connectbaserc) | Public Key (cb_pk_) | npx connectbase deploy | | MCP server (AI tools) | Secret Key (cb_sk_) | Claude, Cursor, Windsurf | | Server-side admin tasks | Secret Key (cb_sk_) | Backend full data access |

⚠️ MCP server rejects Public Keys — you must use a Secret Key (cb_sk_).

⚠️ Never use Secret Keys in frontend code — RLS is bypassed, exposing all data.

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.

Quick Start

import ConnectBase from 'connectbase-client'

// Initialize the SDK — use a Public Key (cb_pk_)
const cb = new ConnectBase({
  apiKey: 'cb_pk_your-public-key'
})

// Create a game room client
const gameClient = cb.game.createClient({
  clientId: 'player-123'
})

// Set up event handlers
gameClient
  .on('onConnect', () => console.log('Connected!'))
  .on('onStateUpdate', (state) => console.log('State:', state))
  .on('onDelta', (delta) => console.log('Delta:', delta.changes))
  .on('onAction', (action) => console.log('Action:', action.type, action.clientId))
  .on('onPlayerJoined', (player) => console.log('Player joined:', player.clientId))
  .on('onPlayerLeft', (player) => console.log('Player left:', player.clientId))

// Connect and create a room
await gameClient.connect()
const state = await gameClient.createRoom({
  maxPlayers: 4,
  tickRate: 64
})

Features

  • Real-time Game Server: WebSocket-based multiplayer game state synchronization
  • Authentication: ID/Password, guest login, and OAuth social login support
  • Database: JSON-based NoSQL database with real-time queries
  • Storage: File storage with CDN support
  • Push Notifications: Cross-platform push notification support
  • WebRTC: Real-time audio/video communication
  • Payments: Subscription and one-time payment support
  • AI Streaming: Real-time AI text generation via WebSocket (Gemini)
  • CLI: Command-line tool for deploying web storage and tunneling local services

CLI

Deploy your web application to Connect Base Web Storage with a single command.

Quick Start

# 1. Initialize (one-time setup)
npx connectbase-client init

# 2. Deploy
npm run deploy

The init command will:

  • Ask for your API Key
  • List existing web storages or create a new one automatically
  • Create a .connectbaserc config file
  • Add .connectbaserc to .gitignore
  • Add a deploy script to package.json (includes build if available)

Commands

| Command | Description | |---------|-------------| | init | Interactive project setup (creates config, adds deploy script) | | deploy <dir> | Deploy files to web storage | | tunnel <port> | Expose a local service to the internet via WebSocket tunnel |

Manual Usage

If you prefer not to use init, you can pass options directly:

npx connectbase-client deploy ./dist -s <storage-id> -k <api-key>

Options

| Option | Alias | Description | |--------|-------|-------------| | --storage <id> | -s | Storage ID | | --api-key <key> | -k | API Key | | --base-url <url> | -u | Custom server URL | | --timeout <sec> | -t | Tunnel request timeout in seconds (tunnel only) | | --max-body <MB> | | Tunnel max body size in MB (tunnel only) | | --help | -h | Show help | | --version | -v | Show version |

Tunnel

Expose a local server to the internet through a secure WebSocket tunnel. Useful for sharing local MCP servers, development servers, or any HTTP service.

# Expose local port 8084 to the internet
npx connectbase-client tunnel 8084 -k <api-key>

# With environment variable
export CONNECTBASE_API_KEY=your-api-key
npx connectbase-client tunnel 8084

# For GPU servers or long-running tasks (e.g., image generation)
npx connectbase-client tunnel 7860 --timeout 300 --max-body 50

The tunnel creates a public URL like https://tunnel.connectbase.world/<tunnel-id>/ that proxies all HTTP requests to your local service.

Plan-based limits: Timeout and body size are clamped to your plan's maximum:

| Plan | Max Timeout | Max Body | |------|-------------|----------| | Free | 60s | 10MB | | Starter | 120s | 25MB | | Pro | 300s | 50MB | | Business | 600s | 100MB |

Features:

  • Per-tunnel timeout and body size configuration
  • Automatic reconnection with exponential backoff
  • Request/response logging in terminal
  • Graceful shutdown with Ctrl+C
  • No external dependencies (uses Node.js built-in modules)

Configuration File

The init command creates .connectbaserc automatically. You can also create it manually:

{
  "apiKey": "your-api-key",
  "storageId": "your-storage-id",
  "deployDir": "./dist"
}

Environment Variables

export CONNECTBASE_API_KEY=your-api-key
export CONNECTBASE_STORAGE_ID=your-storage-id
npx connectbase-client deploy ./dist

Requirements

  • index.html must exist in the root of the deploy directory
  • Supported file types: .html, .css, .js, .json, .svg, .png, .jpg, .gif, .webp, .woff, .woff2, .ttf, .mp3, .mp4, .pdf, .wasm, etc.

SPA Mode

Web Storage supports SPA (Single Page Application) mode, which is enabled by default. When enabled, requests to non-existent paths return index.html instead of 404, allowing client-side routers (React Router, Vue Router, etc.) to handle routing.

You can toggle SPA mode in the Connect Base Console under Storage > Security Settings, or via the API:

# Disable SPA mode (for static sites)
curl -X PUT "https://api.connectbase.world/v1/apps/{appID}/storages/webs/{storageID}" \
  -H "Authorization: Bearer <TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"spa_mode": false}'

Important: When using SPA mode, ensure all asset paths in your HTML are absolute (/assets/...), not relative (./assets/...). For Vite projects, set base: '/' in vite.config.ts.

API Reference

Game Server

GameRoom

The main class for real-time game communication.

const gameClient = cb.game.createClient({
  clientId: 'unique-player-id',    // Required: Unique identifier for this player
  gameServerUrl: 'wss://...',      // Optional: Custom game server URL
  autoReconnect: true,             // Optional: Auto-reconnect on disconnect (default: true)
  maxReconnectAttempts: 5,         // Optional: Max reconnect attempts (default: 5)
  reconnectInterval: 1000,         // Optional: Base reconnect interval in ms (default: 1000)
  connectionTimeout: 10000,        // Optional: Connection timeout in ms (default: 10000)
})
Properties

| Property | Type | Description | |----------|------|-------------| | roomId | string \| null | Current room ID | | state | GameState \| null | Current game state | | isConnected | boolean | Connection status | | isOfflineMode | boolean | Offline mode status | | latency | number | Current latency in ms | | connectionState | ConnectionState | Detailed connection state |

Methods

connect(roomId?: string): Promise<void>

Connect to the game server. Optionally specify a room ID to join immediately.

await gameClient.connect()
// or
await gameClient.connect('existing-room-id')

disconnect(): void

Disconnect from the game server.

gameClient.disconnect()

createRoom(config?: GameRoomConfig): Promise<GameState>

Create a new game room.

const state = await gameClient.createRoom({
  roomId: 'my-custom-room',        // Optional: Custom room ID
  categoryId: 'battle-royale',     // Optional: Room category
  maxPlayers: 100,                 // Optional: Max players (default: 10)
  tickRate: 64,                    // Optional: Server tick rate (default: 64)
  metadata: { map: 'forest' }      // Optional: Custom metadata
})

joinRoom(roomId: string, metadata?: Record<string, string>): Promise<GameState>

Join an existing room.

const state = await gameClient.joinRoom('room-id', {
  team: 'blue',
  displayName: 'Player1'
})

leaveRoom(): Promise<void>

Leave the current room.

await gameClient.leaveRoom()

sendAction(action: GameAction): void

Send a game action to the server.

gameClient.sendAction({
  type: 'move',
  data: { x: 100, y: 200 }
})

gameClient.sendAction({
  type: 'attack',
  data: { targetId: 'enemy-1', damage: 50 }
})

sendChat(message: string): void

Send a chat message to the room.

gameClient.sendChat('Hello everyone!')

requestState(): Promise<GameState>

Request the full current state from the server.

const state = await gameClient.requestState()

listRooms(): Promise<GameRoomInfo[]>

List all available rooms.

const rooms = await gameClient.listRooms()
rooms.forEach(room => {
  console.log(`${room.id}: ${room.playerCount}/${room.maxPlayers}`)
})

ping(): Promise<number>

Measure round-trip time to the server.

const rtt = await gameClient.ping()
console.log(`Latency: ${rtt}ms`)
Event Handlers
gameClient
  .on('onConnect', () => {
    // Called when connected to the server
  })
  .on('onDisconnect', (event: CloseEvent) => {
    // Called when disconnected
  })
  .on('onStateUpdate', (state: GameState) => {
    // Called when full state is received
  })
  .on('onDelta', (delta: GameDelta) => {
    // Called for incremental state updates
    // Use this for efficient state synchronization
  })
  .on('onPlayerJoined', (player: GamePlayer) => {
    // Called when a player joins the room
  })
  .on('onPlayerLeft', (player: GamePlayer) => {
    // Called when a player leaves the room
  })
  .on('onChat', (message: ChatMessage) => {
    // Called when a chat message is received
  })
  .on('onError', (error: ErrorMessage) => {
    // Called on errors
  })
  .on('onPong', (pong: PongMessage) => {
    // Called when pong is received
  })

Offline Mode

Test your game logic locally without a server connection.

// Enable offline mode
gameClient.enableOfflineMode({
  tickRate: 64,
  initialState: {
    players: {},
    objects: []
  },
  simulatedPlayers: [
    { clientId: 'bot-1', joinedAt: Date.now(), metadata: { isBot: 'true' } }
  ]
})

// Update state directly
gameClient.setOfflineState('players.player-1.position', { x: 100, y: 200 })

// Add/remove simulated players
gameClient.addSimulatedPlayer({
  clientId: 'bot-2',
  joinedAt: Date.now(),
  metadata: {}
})
gameClient.removeSimulatedPlayer('bot-2')

// Disable offline mode
gameClient.disableOfflineMode()

Authentication

// ID/Password signup
const result = await cb.auth.signUpMember({
  login_id: 'myuser123',
  password: 'password123',
  nickname: 'John'
})

// ID/Password login
const result = await cb.auth.signInMember({
  login_id: 'myuser123',
  password: 'password123'
})

// Guest login
const guest = await cb.auth.signInAsGuestMember()

// OAuth login (redirect - recommended)
await cb.oauth.signIn('google', 'https://myapp.com/oauth/callback')

// OAuth login (popup - COOP restrictions may apply)
const result = await cb.oauth.signInWithPopup('google', 'https://myapp.com/oauth/callback')

// Sign out
await cb.auth.signOut()

Database

// Query data
const { data } = await cb.database.getData('table-id', {
  where: { status: 'active' },
  limit: 10
})

// Query with field selection (Projection) - improves response speed
const { data } = await cb.database.getData('table-id', {
  select: ['id', 'name', 'thumbnail'],  // Only return these fields
  limit: 20
})

// Exclude specific fields (e.g., large HTML/CSS content)
const { data } = await cb.database.getData('table-id', {
  exclude: ['html_content', 'css_content'],
  limit: 20
})

// Insert data
const newItem = await cb.database.createData('table-id', {
  data: { name: 'John', email: '[email protected]' }
})

// Update data
await cb.database.updateData('table-id', 'data-id', {
  data: { name: 'Jane' }
})

// Delete data
await cb.database.deleteData('table-id', 'data-id')

Aggregation (MongoDB-style Pipeline)

const result = await cb.database.aggregate('table-id', [
  { match: { status: 'active' } },
  { group: { _id: '$category', total: { $sum: '$price' }, count: { $sum: 1 } } },
  { sort: { total: -1 } },
  { limit: 10 }
])
console.log(result.results) // [{ _id: 'electronics', total: 5000, count: 12 }, ...]

Full-Text Search

// Fuzzy search with highlighting
const results = await cb.database.search('table-id', 'smrt phone', ['name', 'description'], {
  fuzzy: true,
  fuzzy_distance: 2,
  highlight: true,
  limit: 10
})

results.results.forEach(item => {
  console.log(item.data.name, item.score, item.highlights)
})

// Autocomplete
const suggestions = await cb.database.autocomplete('table-id', 'sma', 'name', { limit: 5 })

Geo Queries

// Find locations within 5km radius
const nearby = await cb.database.geoQuery('table-id', 'location', {
  near: {
    center: { latitude: 37.5665, longitude: 126.9780 },
    max_distance: 5000
  }
}, { limit: 20 })

nearby.results.forEach(place => {
  console.log(place.data.name, `${place.distance}m away`)
})

Batch & Transactions

// Batch: atomic multi-table operations
await cb.database.batch([
  { type: 'create', table: 'orders', data: { product: 'A', qty: 1 } },
  { type: 'update', table: 'inventory', doc_id: 'item-a', operators: {
    qty: { type: 'increment', value: -1 }
  }},
  { type: 'update', table: 'stats', doc_id: 'daily', operators: {
    order_count: { type: 'increment', value: 1 },
    last_order: { type: 'serverTimestamp' }
  }}
])

// Transaction: read-then-write with ACID guarantees
await cb.database.transaction(
  [{ table: 'accounts', doc_id: 'user-1', alias: 'sender' }],
  [{ type: 'update', table: 'accounts', doc_id: 'user-1', operators: {
    balance: { type: 'increment', value: -100 }
  }}]
)

Populate (Relation Query / JOIN)

// Query with related data populated
const posts = await cb.database.getDataWithPopulate('posts-table', {
  limit: 10,
  populate: [
    { field: 'author_id', from: 'users', as: 'author', select: ['name', 'avatar'] },
    { field: 'id', from: 'comments', as: 'comments', limit: 5, orderBy: 'created_at', order: 'desc' }
  ]
})

Security Rules (RLS)

// Set row-level security rules
await cb.database.createSecurityRule('app-id', {
  table_name: 'posts',
  rules: {
    read: 'true',                              // Anyone can read
    create: 'auth.member_id != null',          // Only authenticated users
    update: 'auth.member_id == data.author_id', // Only author
    delete: 'auth.member_id == data.author_id'
  }
})

// List rules
const rules = await cb.database.listSecurityRules('app-id')

Indexes

// Create unique index
await cb.database.createIndex('app-id', 'table-id', {
  name: 'idx_email_unique',
  fields: ['email'],
  unique: true
})

// Analyze and get recommendations
const analysis = await cb.database.analyzeIndexes('app-id', 'table-id')
analysis.recommendations.forEach(rec => {
  console.log(`Recommended: ${rec.fields.join(', ')} — ${rec.reason}`)
})

Triggers

// Auto-execute function on data change
await cb.database.createTrigger('app-id', {
  name: 'on-order-created',
  table_name: 'orders',
  event: 'create',
  handler_type: 'function',
  handler_id: 'send-notification-fn-id'
})

Lifecycle (TTL / Retention)

// Auto-delete expired sessions
await cb.database.setTTL('app-id', {
  table_name: 'sessions',
  field: 'expires_at',
  enabled: true
})

// Archive old logs after 90 days
await cb.database.setRetentionPolicy('app-id', {
  table_name: 'logs',
  retention_days: 90,
  action: 'archive',
  archive_table: 'archived_logs',
  enabled: true
})

Storage

// 파일 업로드 (UUID 기반 URL - 매번 변경됨)
const result = await cb.storage.uploadFile('storage-id', file)
console.log(result.url)

// 특정 폴더에 업로드
const result = await cb.storage.uploadFile('storage-id', file, 'folder-id')

// 경로 기반 업로드 (고정 URL - Firebase Storage 스타일)
// 같은 경로에 다시 업로드하면 URL이 유지된 채로 파일만 교체
const result = await cb.storage.uploadByPath(
    'storage-id',
    '/profiles/user123/avatar.png',
    file
)
console.log(result.url) // 항상 동일한 URL

// 경로로 파일 조회
const file = await cb.storage.getByPath('storage-id', '/profiles/user123/avatar.png')

// 경로로 URL만 가져오기 (없으면 null)
const url = await cb.storage.getUrlByPath('storage-id', '/profiles/user123/avatar.png')

// 파일 목록 조회
const files = await cb.storage.getFiles('storage-id')

// 파일 삭제
await cb.storage.deleteFile('storage-id', 'file-id')

// 페이지 메타 설정 (SEO / OG 태그 - 웹 스토리지용)
await cb.storage.setPageMeta('web-storage-id', {
    path: '/products/123',
    title: '최신 스마트폰',
    description: '최고의 성능, 최저가 보장',
    image: 'https://example.com/product.jpg',
    og_type: 'product',
    json_ld: JSON.stringify({ "@context": "https://schema.org", "@type": "Product", "name": "스마트폰" }),
    robots_noindex: false        // true면 검색 결과에서 제외
})

// 여러 페이지 일괄 등록
await cb.storage.batchSetPageMeta('web-storage-id', {
    pages: [
        { path: '/products/1', title: '상품 1', description: '설명 1' },
        { path: '/products/2', title: '상품 2', description: '설명 2' },
    ]
})

// 페이지 메타 조회/삭제
const { pages } = await cb.storage.listPageMetas('web-storage-id')
await cb.storage.deletePageMeta('web-storage-id', '/products/123')

Realtime

// Connect to WebSocket
await cb.realtime.connect()

// Subscribe to a category
const subscription = await cb.realtime.subscribe('chat-room')

// Listen for messages
subscription.onMessage((message) => {
  console.log('New message:', message.data)
})

// Send message
await subscription.send({ text: 'Hello!' })

// Unsubscribe
await subscription.unsubscribe()

// Disconnect
await cb.realtime.disconnect()

AI Streaming

Real-time AI text generation using Gemini API through WebSocket.

// Connect first
await cb.realtime.connect()

// Start AI streaming
const session = await cb.realtime.stream(
  [
    { role: 'system', content: 'You are a helpful assistant.' },
    { role: 'user', content: 'Explain quantum computing in simple terms.' }
  ],
  {
    onToken: (token, index) => {
      // Called for each generated token
      process.stdout.write(token)
    },
    onDone: (result) => {
      // Called when generation completes
      console.log('\n\nFull text:', result.fullText)
      console.log('Total tokens:', result.totalTokens)
      console.log('Duration:', result.duration, 'ms')
    },
    onError: (error) => {
      console.error('Stream error:', error.message)
    }
  },
  {
    model: 'gemini-2.0-flash',  // Optional: default is gemini-2.0-flash
    temperature: 0.7,           // Optional: 0.0-2.0, default 0.7
    maxTokens: 1000             // Optional: max output tokens
  }
)

// Stop streaming early if needed
await session.stop()

Stream Options: | Option | Type | Default | Description | |--------|------|---------|-------------| | provider | 'gemini' | 'gemini' | AI provider | | model | string | 'gemini-2.0-flash' | Model name | | system | string | - | System prompt | | temperature | number | 0.7 | Creativity (0.0-2.0) | | maxTokens | number | - | Max output tokens | | sessionId | string | auto | Session tracking ID | | metadata | object | - | Custom metadata |

Stream Result (onDone): | Field | Type | Description | |-------|------|-------------| | sessionId | string | Session ID | | fullText | string | Complete generated text | | totalTokens | number | Total tokens generated | | promptTokens | number | Input prompt tokens | | duration | number | Generation time in ms |

Push Notifications

// Register for push notifications
await cb.push.register({
  token: 'fcm-token-or-apns-token',
  platform: 'android' // or 'ios', 'web'
})

// Subscribe to topics
await cb.push.subscribeToTopic('news')

// Unsubscribe from topic
await cb.push.unsubscribeFromTopic('news')

WebRTC

// API Key/JWT 유효성 사전 검증
const result = await cb.webrtc.validate()
if (result.valid) {
  console.log('인증 성공:', result.app_id)
}

// 로컬 미디어 스트림 가져오기
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true })

// WebRTC 연결
await cb.webrtc.connect({
  roomId: 'live:room-1',
  isBroadcaster: true,
  localStream: stream
})

// 원격 스트림 수신
cb.webrtc.onRemoteStream((peerId, remoteStream) => {
  videoElement.srcObject = remoteStream
})

// 연결 해제
cb.webrtc.disconnect()

Payments & Subscriptions

// Create a subscription
const subscription = await cb.subscription.create({
  planId: 'premium-monthly',
  billingKeyId: 'billing-key-1'
})

// Check subscription status
const status = await cb.subscription.getStatus()

// Cancel subscription
await cb.subscription.cancel()

Types

GameState

interface GameState {
  roomId: string
  state: Record<string, unknown>  // Your game state
  version: number
  serverTime: number
  tickRate: number
  players: GamePlayer[]
}

GameDelta

interface GameDelta {
  fromVersion: number
  toVersion: number
  changes: Array<{
    path: string
    operation: 'set' | 'delete'
    value?: unknown
  }>
  tick: number
}

GamePlayer

interface GamePlayer {
  clientId: string
  joinedAt: number
  metadata?: Record<string, string>
}

ConnectionState

interface ConnectionState {
  status: 'disconnected' | 'connecting' | 'connected' | 'reconnecting' | 'error' | 'offline'
  reconnectAttempt: number
  lastError?: Error
  latency: number
}

Error Handling

try {
  await gameClient.connect()
} catch (error) {
  if (error instanceof Error) {
    console.error('Connection failed:', error.message)
  }
}

// Or use event handlers
gameClient.on('onError', (error) => {
  console.error('Game error:', error.message)
})

Best Practices

State Synchronization

Use delta updates for efficient state synchronization:

gameClient.on('onDelta', (delta) => {
  // Apply only the changes instead of replacing entire state
  for (const change of delta.changes) {
    applyChange(localState, change.path, change.operation, change.value)
  }
})

Reconnection Handling

gameClient.on('onDisconnect', (event) => {
  if (event.code !== 1000) {
    // Show reconnecting UI
    showReconnectingMessage()
  }
})

gameClient.on('onConnect', () => {
  // Reconnected - request full state
  gameClient.requestState()
  hideReconnectingMessage()
})

Latency Compensation

// Measure latency periodically
setInterval(async () => {
  const rtt = await gameClient.ping()
  // Adjust client-side prediction based on latency
  updatePredictionOffset(rtt / 2)
}, 5000)

Examples

Simple Multiplayer Game

import ConnectBase from 'connectbase-client'

const cb = new ConnectBase({ apiKey: 'your-api-key' })
const game = cb.game.createClient({ clientId: `player-${Date.now()}` })

// Local player state
let localPlayer = { x: 0, y: 0 }

game
  .on('onConnect', () => console.log('Connected'))
  .on('onStateUpdate', (state) => {
    // Render all players
    renderPlayers(state.state.players)
  })
  .on('onDelta', (delta) => {
    // Efficient incremental updates
    for (const change of delta.changes) {
      updatePlayer(change.path, change.value)
    }
  })

// Connect and create room
await game.connect()
await game.createRoom({ maxPlayers: 8 })

// Game loop
function gameLoop() {
  // Read input
  const input = getPlayerInput()

  // Send action
  if (input.moved) {
    game.sendAction({
      type: 'move',
      data: { x: input.x, y: input.y }
    })
  }

  requestAnimationFrame(gameLoop)
}

gameLoop()

Chat Application

const game = cb.game.createClient({ clientId: userId })

game.on('onChat', (message) => {
  displayMessage(message.senderId, message.message, message.timestamp)
})

await game.connect()
await game.joinRoom('general-chat')

// Send message
chatInput.addEventListener('submit', () => {
  game.sendChat(chatInput.value)
  chatInput.value = ''
})

License

MIT