@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,
} 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"
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"
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