Browser; server clients
Game channels and directed messages
@silicon-jungle/inkwell-sdk/chatRetained game-channel chat with reconnect/catch-up, directed in-game recipients and server moderation.
Exact source · npm package · Reference version 0.0.8
API at a glance
chat.connect(channel?, { onMessage?, onState?, onModeration? }?): Promise<ChatConnection>chat.list()chat.setDefaultPanelVisible(visible: boolean): Promise<{ visible: boolean }>channel.send(body, { id?, author?, recipients? }?): Promise<ChatReceipt>channel.history(after?): Promise<ChatHistory>channel.onMessage(), onState(), onModeration(): unsubscribechannel.messages / playerId / state / readychannel.close()context.chat.define(name, { serverWritesOnly? }?)context.chat.channel(name).send(), history(), remove(id), clear(), delete()createChat(request?, socketFactory?), createServerChat(request, socketFactory?)
Register initial callbacks in connect options so you receive connection history. Reuse a message UUID when retrying an uncertain send; deduplication lasts only within retained history.
The default platform panel sits left of the game. Browser games with their own chat UI can explicitly hide it for the current play page; new sessions and iframe reloads restore it. Backend chat cannot change panel visibility.
Named channels are joinable by players of the game; the name is not an access secret. Directed recipients are game routing IDs, not a platform DM. Creators can read their game chat.
The hosted context.chat omits connect; an explicit createRuntimeServices().chat client offers server-side subscription when needed.
Factory functions accept an injected service request for adapters and testing; they do not bypass server authorization.
All exported symbols
ChatAuthorChatConnectOptionsChatConnectionChatHistoryChatMessageChatModerationEventChatReceiptChatSendOptionsChatStatechatcreateChatcreateServerChat
Exact TypeScript API
Generated from the SDK source, including input options, return values, public types and overloads. Relative imports below are links between SDK source modules, not additional package subpaths. Only the entrypoints listed in the reference index are supported import paths.
import { type GameServiceRequest } from './game-services.js';
/** Displayed authors are supplied by the game, not verified platform identities. */
export type ChatAuthor = {
displayName: string;
id?: string;
avatarUrl?: string | null;
};
export type ChatMessage = {
id: string;
sequence: number;
channel: string;
senderId: string;
author: ChatAuthor;
body: string;
recipients: string[];
createdAt: string;
};
export type ChatSendOptions = {
id?: string;
author?: ChatAuthor;
recipients?: string[];
};
export type ChatReceipt = {
message: ChatMessage;
duplicate: boolean;
};
export type ChatHistory = {
messages: ChatMessage[];
nextCursor: number;
hasMore: boolean;
retentionHours: number;
removedIds: string[];
retainedFrom: number;
senderIds?: string[];
blockedSenderIds?: string[];
};
export type ChatState = 'connecting' | 'connected' | 'reconnecting' | 'closed';
export type ChatModerationEvent = {
type: 'chat.removed' | 'chat.cleared' | 'chat.channel-deleted' | 'chat.visibility';
channel: string;
id?: string;
throughSequence?: number;
senderIds?: string[];
blockedSenderIds?: string[];
};
type SocketFactory = (url: string) => WebSocket;
export type ChatConnectOptions = {
onMessage?: (message: ChatMessage) => void;
onState?: (state: ChatState) => void;
onModeration?: (event: ChatModerationEvent) => void;
};
export declare class ChatConnection {
readonly channel: string;
private request;
private socketFactory;
private socket;
private stopped;
private generation;
private attempts;
private cursor;
private syncing;
private buffered;
private retained;
private removedIds;
private blockedSenders;
private visibilityVersion;
private clearedThrough;
private pending;
private messageListeners;
private stateListeners;
private moderationListeners;
private retryTimer?;
private renewalTimer?;
private handshakeTimer?;
private pingTimer?;
private firstResolve;
private firstReject;
private connectedOnce;
private currentState;
private currentPlayerId;
readonly ready: Promise<ChatConnection>;
constructor(channel: string, request: GameServiceRequest, options?: ChatConnectOptions, socketFactory?: SocketFactory);
get state(): ChatState;
get playerId(): string;
get messages(): readonly ChatMessage[];
onMessage(listener: (message: ChatMessage) => void): () => void;
onState(listener: (state: ChatState) => void): () => void;
onModeration(listener: (event: ChatModerationEvent) => void): () => void;
private setState;
private receiveMessage;
private updateVisibility;
private open;
private catchUp;
private scheduleReconnect;
private rememberRemoved;
private command;
send(body: string, options?: ChatSendOptions): Promise<ChatReceipt>;
history(after?: number): Promise<ChatHistory>;
private rejectPending;
private clearConnectionTimers;
close(error?: Error): void;
}
export declare function createChat(request?: GameServiceRequest, socketFactory?: SocketFactory): Readonly<{
/** Hide only after your game provides its own chat UI. Applies to this play page, not other players. */
setDefaultPanelVisible: (visible: boolean) => Promise<{
visible: boolean;
}>;
connect: (channel?: string, options?: ChatConnectOptions) => Promise<ChatConnection>;
list: () => Promise<{
channels: {
name: string;
serverWritesOnly: boolean;
}[];
}>;
}>;
export declare function createServerChat(request: GameServiceRequest, socketFactory?: SocketFactory): Readonly<{
define: (name: string, options?: {
serverWritesOnly?: boolean;
}) => Promise<{
name: string;
serverWritesOnly: boolean;
}>;
channel(name?: string): Readonly<{
send: (body: string, options?: ChatSendOptions) => Promise<ChatReceipt>;
history: (after?: number) => Promise<ChatHistory>;
remove: (id: string) => Promise<{
success: true;
}>;
clear: () => Promise<{
success: true;
}>;
delete: () => Promise<{
success: true;
}>;
}>;
connect: (channel?: string, options?: ChatConnectOptions) => Promise<ChatConnection>;
list: () => Promise<{
channels: {
name: string;
serverWritesOnly: boolean;
}[];
}>;
}>;
export declare const chat: Readonly<{
/** Hide only after your game provides its own chat UI. Applies to this play page, not other players. */
setDefaultPanelVisible: (visible: boolean) => Promise<{
visible: boolean;
}>;
connect: (channel?: string, options?: ChatConnectOptions) => Promise<ChatConnection>;
list: () => Promise<{
channels: {
name: string;
serverWritesOnly: boolean;
}[];
}>;
}>;
export {};
Supporting internal type declarations
These clarify types referenced by the generated signatures; do not import these internal paths directly.
game-services.d.ts
export declare class GameServiceError extends Error {
readonly status: number;
readonly code: string;
constructor(message: string, status?: number, code?: string);
}
export type GameServiceRequest = <T>(service: string, request: Record<string, unknown>) => Promise<T>;
export declare const requestGameService: GameServiceRequest;
/** Server-only adapter. Credentials must never be bundled into browser games. */
export declare function createGameServiceRequest(options: {
baseUrl: string;
token: string;
fetch?: typeof fetch;
}): GameServiceRequest;
protocol.d.ts
export declare const SDK_SOURCE: "inkwell-sdk";
export declare const SDK_VERSION: 1;
export type InkwellMessageType = "ready" | "loading.progress" | "loading.error" | "session.complete" | "analytics.track" | "assets.progress" | "player.get" | "presence.get" | "performance.sample" | "backend.connect" | "backend.fetch";
export type InkwellMessage = {
source: typeof SDK_SOURCE;
version: typeof SDK_VERSION;
type: InkwellMessageType;
payload?: Record<string, unknown>;
sentAt: number;
};
export declare function parentOrigin(): string | null;
export declare function requestId(): string;
export declare function emit(type: InkwellMessageType, payload?: Record<string, unknown>): boolean;