PrivChat SDK adapter for Cocos Creator 3.8.x LTS+. Default zero-resource programmatic chat UI plus a node-binding renderer for projects that need designer/Prefab control.
Status: 0.1.0-alpha · API surface frozen between
0.1.x stableminor versions;0.1.x-betamay break. Seedocs/superpowers/specs/2026-05-06-privchat-cocos-v01-design.mdfor the full design.
npm i @privchat/cocos @privchat/sdkcc is a peer provided by your Cocos Creator project.
import { Component, Node, _decorator } from 'cc';
import { PrivchatCocos, PrivchatDarkTheme } from '@privchat/cocos';
import { PrivchatClient } from '@privchat/sdk';
const { ccclass, property } = _decorator;
@ccclass('MyChatScene')
export class MyChatScene extends Component {
@property(Node) chatRoot!: Node;
private mounted: { dispose: () => void } | null = null;
async start() {
const client = new PrivchatClient(/* ... */);
await client.connect();
await client.authenticate(uid, token, deviceId);
this.mounted = PrivchatCocos.mountChatView(this.chatRoot, {
client,
channelId: '10001',
channelType: 1,
theme: PrivchatDarkTheme,
});
}
onDestroy() {
this.mounted?.dispose();
}
}import { instantiate, Label } from 'cc';
PrivchatCocos.bindChatView({
client,
channelId: '10001',
channelType: 1,
nodes: { root, messageContent, inputBox, sendButton },
hook: {
renderMessage(item, _i, _container) {
// Use your own Prefab; return the created Node.
const node = instantiate(yourBubblePrefab);
node.getComponentInChildren(Label)!.string = item.fallbackText;
return node;
},
},
});ThemeConfig covers colors, radius, spacing, fontSize. Pass your own when calling mountChatView:
PrivchatCocos.mountChatView(root, {
client,
channelId: 'c1',
channelType: 1,
theme: {
colors: { /* override any subset; full default in src/chat/chat-theme.ts */ },
radius: { bubble: 14, input: 12 },
spacing: { xs: 4, sm: 8, md: 12, lg: 16 },
fontSize: { message: 15, time: 11, input: 15 },
},
});The library never owns global UI. Subscribe explicitly:
onError(err)— unrecoverable errors (auth, RPC failure, sustained disconnect)onToast(msg)— soft messages ("发送失败", "加载历史失败")
ChatView itself shows inline status (failed bubble, history-load failed banner) via the ViewModel.
Multiple mountChatView calls share the same PrivchatClient cache but maintain independent UI state (input draft, scroll position, virtual list pool).
mountChatView returns { dispose, viewModel, controller }. Call dispose() from your Component's onDestroy(). Idempotent — safe to call twice.
For game UIs that keep a chat panel mounted across navigations (split-screen
layouts, sidebar chat, ChatPanel-style hosts), do not dispose and remount
the view every time the user picks a different friend / room. Instead, hold
on to the controller returned by mountChatView and call:
await controller.setChannel(nextChannelId, nextChannelType);ChatController.setChannel is the supported API for in-place channel swaps:
- Bumps
channelEpochso any in-flightstart()/loadMore()/send()capturing the old epoch short-circuits cleanly when its await resolves. - Tears down only the per-channel
observeConversationsubscription, not the controller-lifetime composite — listeners, error handler, toast hook, and self uid are preserved. - Resets the view-model to an empty state for the new channel and runs
start()against it. The renderer subscribed to the controller re-renders automatically — no UI re-creation, no flash. - Cache-first behavior carries over: the local IndexedDB window is shown first, then the server's latest window replaces it.
Use setChannel whenever the chat surface is reused. Use a fresh
mountChatView only when the chat region itself is being torn down
(scene swap, full-screen back navigation).
Demo note: the bundled
privchat-cocos-demouses a push-pop page flow (chat → back → friends → another friend → chat). The friends page reclaims the same root node, so the demo intentionally dispose+remounts on each navigation to keep lifecycle simple. This is not the recommended pattern for shipping product UIs.
See examples/cocos-starter/README.md.
| Version | Theme |
|---|---|
| v0.1 | Programmatic ChatView |
| v0.1.1 | Contact list (mountContactList + SdkFriendsSource), Room subscription headless API (createRoomSubscriptionController), lazy nickname/avatar fetch via account/user/detail (this release) |
| v0.2 | Prefab-binding enhancements: custom MessageRenderer, avatar image loader, navigation callbacks |
| v0.3 | IM polish: dynamic-height bubbles, image messages, scroll anchor preservation, SDK room_publish_received event surface (cross-package) |
| v0.3.5 | Embeddable ChatPanel (see below) — keep-alive / hide-reuse lives here, not on the basic ChatView path |
| v0.4 | Platform evaluation: extract @privchat/cocos-ui if a second consumer arrives |
v0.1 exposes a single ChatView (one channel, message list + input). Real games often need to combine a conversation list with a detail view. The host game owns container placement and orientation policy — @privchat/cocos will render whichever mode you ask for into the Node you provide.
Planned API (v0.3.5):
PrivchatCocos.mountChatPanel(containerNode, {
client,
mode: 'split' | 'conversationList' | 'conversationDetail',
// ... mode-specific options
});Three modes:
conversationDetail— single channel's messages + input. Equivalent tomountChatView; preserved for API symmetry.conversationList— conversation/contact list only. Host receivesonSelectConversation(conv)and decides what to do (open another panel, navigate, etc.).split— left list + right detail, with built-in select-and-show wiring. For landscape game side-panels, PC/tablet large screens.
What the library will NOT do (these stay with the host game):
- Auto-detect orientation / aspect ratio
- Manage half-screen / floating-overlay / fullscreen layout
- Provide back-button / page navigation
- Show toasts or modal dialogs
The library renders into the container the host provides. Layout, overlays, and orientation belong to the game's UI system. v0.1 already follows this model with mountChatView; v0.3.5 only widens the scope of what's renderable.
Apache-2.0.