Channels
The SDK exposes app-wide message channels via client.channels (CRUD and
membership) and client.udp (publishing and receiving messages). A channel
message reaches every member of the channel regardless of where they are in the
world. See the Game API Channels guide for the full
permission model.
Manage channels
Channels are a Game API surface, so they need an app-scoped token: log in on an identity client, mint a token for the app, and drive channels from a per-game client (see Portals & app-scoped tokens).
import {
BrowserLocalStorageTokenStore,
createCrowdyClient,
} from '@crowdedkingdoms/crowdyjs';
const identity = createCrowdyClient({
managementUrl: 'https://dev-management-api.crowdedkingdoms.com',
tokenStore: new BrowserLocalStorageTokenStore('crowdyjs:session'),
});
await identity.auth.devLogin('player@example.com'); // passwordless sign-in (dev/test); see /crowdyjs/readme#sign-in-with-clientauth-passwordless
const appToken = await identity.portal.mintAppToken('1');
const game = createCrowdyClient({
managementUrl: 'https://dev-management-api.crowdedkingdoms.com',
httpUrl: appToken.gameApiUrl ?? 'https://dev-game-api.crowdedkingdoms.com',
wsUrl: appToken.gameApiWsUrl ?? 'wss://dev-game-api.crowdedkingdoms.com',
tokenStore: new BrowserLocalStorageTokenStore('crowdyjs:app:1'),
});
game.setToken(appToken.token);
// Create an open chat channel (members can post by default).
const channel = await game.channels.create({
appId: '1',
name: 'Global Trade',
membershipPolicy: 'open',
membersCanSend: true,
});
await game.channels.join(channel.groupId);
const mine = await game.channels.mine('1'); // channels I belong to
const members = await game.channels.members(channel.groupId);
Other methods mirror the GraphQL surface: list, get, roles, policy,
update, remove, requestToJoin, leave, addMember, removeMember,
setMemberRoles, createRole, updateRole, deleteRole, and setPolicy.
Send and receive messages
Subscribe with a channelMessage handler — passing the appId as the second
argument, since every realtime subscription is app-scoped — then publish with
client.udp.sendChannelMessage. The first subscriber opens the shared realtime
WebSocket; publishing opens the UDP proxy session automatically.
const unsubscribe = game.udp.subscribe(
{
channelMessage: (msg) => {
// msg.channelId, msg.uuid (sender), msg.payload (base64), msg.sequenceNumber
const text = Buffer.from(msg.payload, 'base64').toString();
console.log(`[${msg.channelId}] ${text}`);
},
genericError: (err) => console.warn('channel error', err.errorCode),
},
'1', // the app whose realtime session this subscription opens
);
await game.udp.sendChannelMessage({
channelId: channel.groupId,
uuid: myActorUuid, // 32-byte UTF-8 actor id
payload: Buffer.from('hello').toString('base64'),
sequenceNumber: 1,
});
// later
unsubscribe();
Notes:
- The sender must hold the channel
send_messagespermission; otherwise the publish is rejected (delivered as agenericErrorwithUNAUTHORIZED). - The sender receives no echo of its own message.
- Payloads are opaque (base64) and messages are ephemeral — keep your own scrollback client-side if needed.