@scenarix/jaduspine-sdk¶
Internal realtime messaging SDK wrapping Centrifugo for WebSocket and HTTP communication.
Installation¶
Note: This is a private package hosted on GitHub Packages.
1. Configure npm registry¶
Create or update .npmrc in your project root:
@scenarix:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GITHUB_PKG_TOKEN}
2. Set up authentication¶
Create a GitHub Personal Access Token with read:packages scope:
export GITHUB_PKG_TOKEN=ghp_xxxxxxxxxxxx
3. Install¶
npm install @scenarix/jaduspine-sdk
# or
yarn add @scenarix/jaduspine-sdk
# or
pnpm add @scenarix/jaduspine-sdk
Frontend Integration¶
React Provider & Hook¶
The SDK provides a React context for sharing a single WebSocket connection across your app.
1. Wrap your app with the provider:
// app/providers.tsx
import {
JaduSpineProvider,
FrontendChannels,
} from "@scenarix/jaduspine-sdk/react";
export function Providers({
children,
token,
}: {
children: React.ReactNode;
token: string;
}) {
return (
<JaduSpineProvider
wsUrl={process.env.NEXT_PUBLIC_CENTRIFUGO_WS_URL!}
token={token}
frontend={FrontendChannels.MINIMATICS}
debug={process.env.NODE_ENV === "development"}
>
{children}
</JaduSpineProvider>
);
}
2. Use the hook in any component:
import { useEffect } from "react";
import { useJaduSpine, ConnectionState } from "@scenarix/jaduspine-sdk/react";
function NotificationListener() {
const { connectionState, onMessage, userId } = useJaduSpine();
useEffect(() => {
const unsubscribe = onMessage((msg) => {
console.log("Received:", msg.payload);
// Handle message based on msg.type
});
return unsubscribe;
}, [onMessage]);
return (
<div>
{connectionState === ConnectionState.CONNECTED
? `Connected as ${userId}`
: "Connecting..."}
</div>
);
}
Provider Props¶
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
wsUrl |
string |
Yes | - | Centrifugo WebSocket URL |
token |
string |
Yes | - | JWT token (must contain sub claim) |
frontend |
FrontendChannels |
Yes | - | Frontend app identifier |
debug |
boolean |
No | false |
Enable debug logging |
autoConnect |
boolean |
No | true |
Auto-connect on mount |
Hook Return Value¶
const {
connectionState, // ConnectionState: DISCONNECTED | CONNECTING | CONNECTED
userId, // string | null - extracted from JWT
clientId, // string | null - unique client ID
connect, // () => void - manual connect
disconnect, // () => void - disconnect
destroy, // () => void - cleanup
onMessage, // (handler) => unsubscribe - listen for messages
onConnectionStateChange, // (handler) => unsubscribe - listen for state changes
onEvent, // (handler) => unsubscribe - listen for all events
} = useJaduSpine();
Backend Integration¶
Singleton Pattern (Recommended)¶
Initialize once at server startup, then use JaduSpineBackend.publish() from anywhere in your codebase.
1. Initialize at server bootstrap:
// server/index.ts or app initialization
import { JaduSpineBackend } from "@scenarix/jaduspine-sdk";
await JaduSpineBackend.init({
apiUrl: process.env.CENTRIFUGO_API_URL!,
apiKey: process.env.CENTRIFUGO_API_KEY!,
debug: process.env.NODE_ENV === "development",
});
// Throws if health check fails (server unreachable or invalid API key)
2. Use anywhere in your codebase:
// services/notifications.ts
import {
JaduSpineBackend,
FrontendChannel,
FrontendChannels,
} from "@scenarix/jaduspine-sdk";
export async function notifyUser(userId: string, message: string) {
await JaduSpineBackend.publish(
new FrontendChannel(FrontendChannels.MINIMATICS, userId),
{ type: "notification", message }
);
}
// api/jobs/complete.ts
import {
JaduSpineBackend,
FrontendChannel,
FrontendChannels,
} from "@scenarix/jaduspine-sdk";
export async function handleJobComplete(userId: string, jobId: string) {
await JaduSpineBackend.publish(
new FrontendChannel(FrontendChannels.STUDIO, userId),
{ type: "job:complete", jobId, timestamp: Date.now() }
);
}
Init Configuration¶
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
apiUrl |
string |
Yes | - | Centrifugo HTTP API URL |
apiKey |
string |
No | "" |
API key for authentication |
clientId |
string |
No | Auto-generated | Custom client identifier |
debug |
boolean |
No | false |
Enable debug logging |
skipHealthCheck |
boolean |
No | false |
Skip server health check on init |
retryAttempts |
number |
No | 0 |
Number of retry attempts on publish fail |
retryDelay |
number |
No | 1000 |
Delay between retries (ms) |
Static Methods¶
// Initialize (call once)
await JaduSpineBackend.init(config);
// Check if initialized
JaduSpineBackend.isInitialized(); // boolean
// Publish to a channel
await JaduSpineBackend.publish(channel, payload, type?);
// Broadcast to multiple channels
await JaduSpineBackend.broadcast(channels, payload, type?);
// Publish pre-formatted SpineMessage
await JaduSpineBackend.publishRaw(channel, message);
// Get underlying JaduSpineHttp instance
JaduSpineBackend.getInstance();
// Destroy singleton (for reinit or shutdown)
JaduSpineBackend.destroy();
Channel Classes¶
import {
FrontendChannel,
FrontendChannels,
BackendChannel,
BackendChannels,
CustomChannel,
} from "@scenarix/jaduspine-sdk";
// User-scoped frontend channel: "frontend:minimatics#user123"
const userChannel = new FrontendChannel(FrontendChannels.MINIMATICS, "user123");
console.log(userChannel.value); // "frontend:minimatics#user123"
// Backend channel: "backend:studio"
const studioChannel = new BackendChannel(BackendChannels.STUDIO);
console.log(studioChannel.value); // "backend:studio"
// Custom named channel: "custom:proj_abc123"
const projectChannel = new CustomChannel("proj_abc123");
console.log(projectChannel.value); // "custom:proj_abc123"
Available Channels¶
// Frontend (user-scoped)
FrontendChannels.MINIMATICS; // "minimatics"
FrontendChannels.STORYDESK; // "storydesk"
FrontendChannels.STUDIO; // "studio"
FrontendChannels.PLAYGROUND; // "playground"
// Backend (service-to-service)
BackendChannels.STUDIO; // "studio"
BackendChannels.STORYDESK; // "storydesk"
BackendChannels.PLAYGROUND; // "playground"
// Custom (broadcast / group / project-scoped)
new CustomChannel("any-name-you-choose"); // "custom:any-name-you-choose"
Custom Channels¶
CustomChannel enables broadcasting to any named group — project members, shared rooms, global feeds — without user-scoping.
Backend: publish to a custom channel
import { JaduSpineBackend, CustomChannel } from "@scenarix/jaduspine-sdk";
await JaduSpineBackend.publish(
new CustomChannel("proj_abc123"),
{ topic: "project:update", data: { action: "scene_saved", userId } }
);
Frontend: subscribe via useChannel
import { useChannel, CustomChannel } from "@scenarix/jaduspine-sdk/react";
function ProjectPanel({ projectId }: { projectId: string }) {
const [events, setEvents] = useState<ProjectEvent[]>([]);
useChannel<ProjectEvent>(
new CustomChannel(projectId),
(msg) => setEvents((prev) => [...prev, msg.data])
);
return <EventFeed events={events} />;
}
Multiple components subscribing to the same channel share one underlying WebSocket subscription. The subscription is cleaned up when the last component unmounts.
Centrifugo configuration
The custom namespace must be enabled in your Centrifugo config:
{
"namespaces": [
{
"name": "custom",
"allow_subscribe_for_client": true
}
]
}
Error Handling¶
import { SpineError, SpineErrorCode } from "@scenarix/jaduspine-sdk";
try {
await JaduSpineBackend.publish(channel, data);
} catch (error) {
if (error instanceof SpineError) {
switch (error.code) {
case SpineErrorCode.HTTP_UNAUTHORIZED:
console.error("Invalid API key");
break;
case SpineErrorCode.HTTP_REQUEST_FAILED:
console.error("Request failed:", error.message);
break;
case SpineErrorCode.INVALID_STATE:
console.error("Not initialized");
break;
}
}
}
Publishing¶
Ensure your
GITHUB_PKG_TOKENhaswrite:packagesscope (not justread:packages).
# 1. Run pre-publish checks (build, typecheck, test)
npm run prepublish:check
# 2. Bump version in package.json
# 3. Publish to GitHub Packages
npm publish
# Or dry-run first
npm run publish:dry
Available Scripts¶
| Script | Description |
|---|---|
npm run build |
Build the SDK (CJS + ESM + types) |
npm run typecheck |
Run TypeScript type checking |
npm run test |
Run tests |
npm run prepublish:check |
Clean, build, typecheck, and test |
npm run publish:dry |
Dry-run publish to verify package |
Documentation¶
- AUTH_README.md - JWT authentication setup and token generation
- CONTRIBUTING.md - Development setup and playground instructions
- infra/DEPLOYMENT.md - AWS deployment playbook (ECS/Fargate + ALB + Cloudflare DNS + Terraform)
- CHANGELOG.md - Version history and release notes
- Specification - Architecture and design decisions