Skip to content

@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

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_TOKEN has write:packages scope (not just read: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