Server API
The full reference for @useceleris/server covers createSigner, the claims surface, and the connection to Celeris from a backend.
npm install @useceleris/server
The package runs on trusted servers only: Node.js 22.15+, Bun 1.3+ and Deno 2.5+. This package holds your signing secret and must never reach a browser or mobile bundle.
The current version is 1.0.0. It declares @useceleris/client ^1.0.0 as a peer dependency and uses it for types only, so client 1.1.0 is in its range.
createSigner(options)
function createSigner(options: SignerOptions): Signer;
type SignerOptions = {
clientId: string; // no colon, no CR/LF
signingSecret: string;
clock?: () => number; // Unix ms; a test seam
};
type Signer = { sign(claims: SigningClaims): SignedCredentials };
type SignedCredentials = {
readonly payload: string;
readonly signature: string;
};
sign() is synchronous and deterministic for fixed inputs. It takes no cancellation signal and returns no promise.
import { createSigner } from "@useceleris/server";
const signer = createSigner({
clientId: process.env.CELERIS_CLIENT_ID!,
signingSecret: process.env.CELERIS_SIGNING_SECRET!,
});
const credentials = signer.sign({
channels: { kind: "restricted", references: ["room-42"] },
permissions: {
kind: "restricted",
segments: [{ segmentId: "chat", read: true, write: true }],
},
reference: "user-8317",
replay: { lookbackMs: 30_000 },
});
Pass payload and signature back to the browser unchanged. They are opaque. Never parse, re-encode or cache them.
The claims surface
type SigningClaims = {
readonly channels: ChannelScope;
readonly permissions: SegmentPermissions;
readonly reference?: string;
readonly replay?: boolean | { readonly lookbackMs: number };
readonly allowEcho?: boolean;
};
| Field | Shape | Notes |
|---|---|---|
channels | { kind: "all" } or { kind: "restricted", references } | References are 1–255 characters of [a-zA-Z0-9_-], unique, at least one. An empty list is rejected, never read as "all". |
permissions | { kind: "all", read, write } or { kind: "restricted", segments } | Flags are explicit booleans. A segment id is nonempty, with no CR/LF, and must not repeat in the list. A restricted list can be empty, which grants nothing. |
reference | optional string | Identity label in message metadata and presence. Nonempty, no colon, no CR/LF. Omit to let the server generate one. |
replay | boolean or { lookbackMs } | Defaults to false. Lookback is 0–4294967295 ms. |
allowEcho | optional boolean | Defaults to false: a connection does not receive its own publishes. |
type SegmentClaim = {
readonly segmentId: string;
readonly read: boolean;
readonly write: boolean;
};
Unrestricted access is always an explicit opt-in:
signer.sign({
channels: { kind: "all" },
permissions: { kind: "all", read: true, write: false },
});
write: true but read: false can publish and cannot subscribe. The server refuses its subscription with a PermissionDeniedError. On default, such a token receives nothing, with no error. If a client connects but never receives messages, check read first.createCredentialProvider(options)
Sometimes a backend is itself a realtime client, for example a worker that publishes events. This function bridges the signer to the client package's provider.
function createCredentialProvider(
options: CredentialProviderOptions
): CredentialProvider;
type CredentialProviderOptions = {
signer: Signer;
claims: (
request: CredentialRequest
) => SigningClaims | Promise<SigningClaims>;
};
| Option | Required | Meaning |
|---|---|---|
signer | Yes | A signer with a synchronous sign(claims) method |
claims | Yes | Server-owned authorization callback, called for each attempt |
The returned provider checks request.signal before and after it awaits the claims, and then it signs. If the signal aborts, the provider does not sign. You cannot interrupt the synchronous signer itself. The server package re-exports CredentialRequest. See the client types for its fields.
import { createClient } from "@useceleris/client";
import { createCredentialProvider, createSigner } from "@useceleris/server";
const signer = createSigner({
clientId: process.env.CELERIS_CLIENT_ID!,
signingSecret: process.env.CELERIS_SIGNING_SECRET!,
});
const client = createClient({
credentialProvider: createCredentialProvider({
signer,
claims: (request) => ({
channels: { kind: "restricted", references: [request.channelReference] },
permissions: {
kind: "restricted",
segments: [{ segmentId: "chat", read: true, write: true }],
},
reference: "worker-1",
replay:
request.replayLookbackMs !== undefined
? { lookbackMs: Math.min(request.replayLookbackMs, 30_000) }
: false,
}),
}),
});
const channel = client.channel("room-42");
await channel.connect();
Your claims callback runs freshly on every attempt and is the sole authority over scope. The provider copies nothing from the request into the claims automatically. From there you use the ordinary client API to publish and subscribe.
Trusted worker code chooses this example's channel. In an HTTP credential endpoint, authorize the requested channel against the verified user's access before you sign it. The 30-second replay cap is an example application policy, not a retention promise. See Authentication and Reconnection and Recovery.
A fresh signature does not guarantee a particular credential lifetime, and it does not revoke an existing connection. Keep signed credentials confidential and pass them unchanged. The signer has no expiry-duration or revocation option.
Errors
Both failure modes carry a stable code. A Configuration message names each option or claim that failed and the rule it broke, such as Invalid claims. channels.references[0]: Must not be empty.. The message never echoes your input, secrets or claims. The package deliberately does not export the error classes. Identify failures by code.
code | Raised when |
|---|---|
Configuration | Invalid signer options or claims. Fix the call site. |
SigningFailed | Encoding or cryptographic failure during signing |
Both options objects are strict: an unexpected key is a Configuration error, not a silently ignored field.
This rule applies to signer and credential-provider options. The SDK validates claims separately and strips unknown claim keys. Do not rely on an unknown key to grant access or enforce a restriction.