← Complete SDK reference

Browser; custom transport adapters

HTTP, messages and actions

@silicon-jungle/inkwell-sdk/backend

Call your game’s on-demand server or exchange named reliable/unreliable messages and request/response actions.

Exact source · npm package · Reference version 0.0.8

API at a glance

  • backend.request(path, { timeoutMs?, ...RequestInit }?): Promise<Response>
  • backend.connect<Protocol>({ transport?, actionTimeoutMs?, connectTimeoutMs?, unreliableFallback?, signal? }?): Promise<BackendConnection<Protocol>>
  • connection.on(name, handler): unsubscribe
  • connection.sendReliable(name, payload): Promise<void>
  • connection.sendUnreliable(name, payload): boolean
  • connection.negotiateBinaryEvents({ timeoutMs? }?): Promise<boolean>
  • connection.binaryEvents / onBinary(name, (bytes, delivery) => {}): unsubscribe
  • connection.sendBinaryReliable(name, bytes) / sendBinaryUnreliable(name, bytes)
  • connection.action(name, input, { timeoutMs?, signal? }?): Promise<Output>
  • connection.transportKind / connection.capabilities
  • connection.close(code?, reason?): void
  • connectBackend(), requestBackend(), BackendConnection
  • BackendTransport, WebSocketBackendTransport, WebTransportBackendTransport
  • BackendConnectionError, BackendActionError

HTTP paths must be relative to this game backend, not arbitrary remote URLs. Request and response bodies are bounded to 8 MiB; GET/HEAD cannot contain bodies. Inspect response.ok for application errors.

Actions use reliable delivery. A timeout or abort stops waiting locally; it does not roll back a server-side operation. Design retries with your own idempotency key.

sendUnreliable returns local admission, not delivery confirmation. capabilities.unreliable distinguishes native, emulated and unavailable delivery. Fallback can be reliable or drop; inspect it before assuming loss or ordering behaviour.

Pooled games use direct browser-to-game WebTransport/QUIC with no gameplay TCP relay. The backend connection has no automatic reconnect/replay guarantee; recreate it and resynchronize your game.

SDK 0.0.7 binary events require server opt-in and reliable negotiation. Unsupported servers and negotiation timeouts return false; transport failures reject. Negotiate before joining the binary game protocol. Raw Uint8Array events have separate handlers; ordinary JSON payloads and actions are unchanged.

Typed protocols describe clientEvents, serverEvents and actions. TypeScript types do not validate untrusted network payloads at runtime.

Examples, policies and operational limits →

All exported symbols

  • AnyBackendProtocol
  • BackendActionError
  • BackendConnection
  • BackendConnectionError
  • BackendProtocol
  • BackendTransport
  • BackendTransportCapabilities
  • BackendTransportKind
  • UnreliableDelivery
  • WebSocketBackendTransport
  • WebTransportBackendTransport
  • backend
  • connectBackend
  • requestBackend

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 Delivery } from './wire.js';
export type BackendProtocol = {
    clientEvents: Record<string, unknown>;
    serverEvents: Record<string, unknown>;
    actions: Record<string, {
        input: unknown;
        output: unknown;
    }>;
};
export type AnyBackendProtocol = {
    clientEvents: Record<string, unknown>;
    serverEvents: Record<string, unknown>;
    actions: Record<string, {
        input: unknown;
        output: unknown;
    }>;
};
type EventName<T> = Extract<keyof T, string>;
type ActionInput<T> = T extends {
    input: infer Input;
} ? Input : never;
type ActionOutput<T> = T extends {
    output: infer Output;
} ? Output : never;
export type BackendTransportKind = 'webtransport' | 'websocket' | 'custom';
export type UnreliableDelivery = 'native' | 'emulated' | 'unavailable';
export type BackendTransportCapabilities = Readonly<{
    unreliable: UnreliableDelivery;
    maxUnreliableFrameBytes: number;
}>;
export interface BackendTransport {
    readonly kind: BackendTransportKind;
    readonly capabilities: BackendTransportCapabilities;
    setHandlers(handlers: {
        frame(frame: Uint8Array, delivery: Delivery): void;
        close(reason?: Error): void;
    }): void;
    sendReliable(frame: Uint8Array): Promise<void>;
    sendUnreliable(frame: Uint8Array): boolean;
    close(code?: number, reason?: string): void;
}
export declare class BackendConnectionError extends Error {
    constructor(message: string);
}
export declare class BackendActionError extends Error {
    readonly code: string;
    constructor(code: string, message: string);
}
type BackendConnectionOptions = {
    transport?: BackendTransport;
    actionTimeoutMs?: number;
    connectTimeoutMs?: number;
    unreliableFallback?: 'reliable' | 'drop';
    signal?: AbortSignal;
};
export declare class WebSocketBackendTransport implements BackendTransport {
    private readonly socket;
    private readonly unreliableFallback;
    readonly kind: "websocket";
    readonly capabilities: BackendTransportCapabilities;
    private handlers;
    private incoming;
    private constructor();
    static connect(url: string, timeoutMs: number, signal?: AbortSignal, unreliableFallback?: 'reliable' | 'drop'): Promise<WebSocketBackendTransport>;
    setHandlers(handlers: Parameters<BackendTransport['setHandlers']>[0]): void;
    sendReliable(frame: Uint8Array): Promise<void>;
    sendUnreliable(frame: Uint8Array): boolean;
    close(code?: number, reason?: string): void;
}
export declare class WebTransportBackendTransport implements BackendTransport {
    private readonly transport;
    readonly kind: "webtransport";
    readonly capabilities: BackendTransportCapabilities;
    private handlers;
    private readonly reliableDecoder;
    private readonly reliableReader;
    private readonly reliableWriter;
    private readonly datagramReader;
    private readonly datagramWriter;
    private reliableWrites;
    private reading;
    private closed;
    private constructor();
    static connect(url: string, timeoutMs: number, signal?: AbortSignal, serverCertificateHashes?: readonly string[]): Promise<WebTransportBackendTransport>;
    setHandlers(handlers: Parameters<BackendTransport['setHandlers']>[0]): void;
    sendReliable(frame: Uint8Array): Promise<void>;
    sendUnreliable(frame: Uint8Array): boolean;
    close(code?: number, reason?: string): void;
    private readReliable;
    private readDatagrams;
    private finish;
}
export declare function requestBackend(path: string, init?: RequestInit & {
    timeoutMs?: number;
}): Promise<Response>;
export declare class BackendConnection<Protocol extends BackendProtocol = AnyBackendProtocol> {
    private readonly transport;
    private readonly actionTimeoutMs;
    private readonly handlers;
    private readonly binaryHandlers;
    private binaryEnabled;
    private binaryNegotiation;
    private binaryRequestId;
    private readonly pending;
    private closed;
    constructor(transport: BackendTransport, actionTimeoutMs?: number);
    get transportKind(): BackendTransportKind;
    get capabilities(): Readonly<{
        unreliable: UnreliableDelivery;
        maxUnreliableFrameBytes: number;
    }>;
    get binaryEvents(): boolean;
    /** Negotiate once per connection. Old/opted-out servers keep JSON support. */
    negotiateBinaryEvents(options?: {
        timeoutMs?: number;
    }): Promise<boolean>;
    onBinary(name: string, handler: (payload: Uint8Array, delivery: Delivery) => void): () => void;
    sendBinaryReliable(name: string, payload: Uint8Array): Promise<void>;
    sendBinaryUnreliable(name: string, payload: Uint8Array): boolean;
    on<Name extends EventName<Protocol['serverEvents']>>(name: Name, handler: (payload: Protocol['serverEvents'][Name]) => void): () => void;
    sendReliable<Name extends EventName<Protocol['clientEvents']>>(name: Name, payload: Protocol['clientEvents'][Name]): Promise<void>;
    sendUnreliable<Name extends EventName<Protocol['clientEvents']>>(name: Name, payload: Protocol['clientEvents'][Name]): boolean;
    action<Name extends EventName<Protocol['actions']>>(name: Name, payload: ActionInput<Protocol['actions'][Name]>, options?: {
        timeoutMs?: number;
        signal?: AbortSignal;
    }): Promise<ActionOutput<Protocol['actions'][Name]>>;
    close(code?: number, reason?: string): void;
    private receive;
    private handleClose;
    private rejectPending;
    private assertOpen;
    private assertBinaryEnabled;
}
export declare function connectBackend<Protocol extends BackendProtocol = AnyBackendProtocol>(options?: BackendConnectionOptions): Promise<BackendConnection<Protocol>>;
export declare const backend: Readonly<{
    connect: typeof connectBackend;
    request: typeof requestBackend;
}>;
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;