Kraken Integration Guide

Real-time collaborative sync for local-first applications

๐Ÿš€ Quick Start

Kraken provides WebSocket-based real-time sync for Yjs documents. Each app gets isolated namespaces for data separation.

1. Register Your App

Before connecting, your app must be registered with Kraken (contact admin or use the API):

POST /api/apps
Authorization: Bearer YOUR_ADMIN_TOKEN
Content-Type: application/json

{
  "app_id": "my-app",
  "name": "My Application",
  "allowed_origins": ["https://my-app.example.com"],
  "owner_email": "dev@example.com"
}

2. Connect via WebSocket

const ws = new WebSocket(
  'wss://kraken.helixpods.ai/doc/my-document?token=YOUR_TOKEN'
);

๐Ÿ” Authentication

Kraken uses a two-layer security model:

Layer 1: Master Token

A shared secret that proves your app is authorized to use Kraken. Pass it as:

  • Query parameter: ?token=YOUR_TOKEN
  • Header: Authorization: Bearer YOUR_TOKEN

Layer 2: Origin-Based Namespacing

Your app's origin (domain) determines which namespace your rooms belong to. This is automatic - Kraken reads the Origin header from WebSocket connections.

Important: The master token is shared across all Nexartis apps. Data isolation is enforced by origin-based namespacing, not the token.

๐Ÿข Multi-Tenancy

Each registered app gets isolated data:

Your Request Internal Room ID
/doc/readme from app-a.example.com app-a:readme
/doc/readme from app-b.example.com app-b:readme
Automatic Isolation: Apps cannot access each other's documents, even with the same room name.

๐Ÿ“ก API Reference

GET /health

Health check endpoint. No authentication required.

curl https://kraken.helixpods.ai/health

WS /doc/:roomName

WebSocket endpoint for Yjs sync. Requires authentication.

wss://kraken.helixpods.ai/doc/my-room?token=TOKEN

GET /api/apps

List registered apps. Admin authentication required.

POST /api/apps

Register a new app. Admin authentication required.

{
  "app_id": "string (required)",
  "name": "string (required)",
  "allowed_origins": ["array of strings (required)"],
  "description": "string (optional)",
  "owner_email": "string (optional)"
}

๐Ÿ’ป Client Integration

Using y-websocket (Recommended)

import * as Y from 'yjs';
import { WebsocketProvider } from 'y-websocket';

const doc = new Y.Doc();
const provider = new WebsocketProvider(
  'wss://kraken.helixpods.ai',
  'my-document',
  doc,
  { params: { token: 'YOUR_TOKEN' } }
);

provider.on('status', ({ status }) => {
  console.log('Connection status:', status);
});

// Use your Yjs document
const text = doc.getText('content');
text.insert(0, 'Hello, collaborative world!');

Using Native WebSocket

import * as Y from 'yjs';
import * as syncProtocol from 'y-protocols/sync';
import * as encoding from 'lib0/encoding';
import * as decoding from 'lib0/decoding';

const doc = new Y.Doc();
const ws = new WebSocket('wss://kraken.helixpods.ai/doc/my-room?token=YOUR_TOKEN');
ws.binaryType = 'arraybuffer';

ws.onopen = () => {
  // Send sync step 1
  const encoder = encoding.createEncoder();
  encoding.writeVarUint(encoder, 0); // message type: sync
  syncProtocol.writeSyncStep1(encoder, doc);
  ws.send(encoding.toUint8Array(encoder));
};

ws.onmessage = (event) => {
  const decoder = decoding.createDecoder(new Uint8Array(event.data));
  const messageType = decoding.readVarUint(decoder);
  // Handle sync messages...
};

React Example

import { useEffect, useState } from 'react';
import * as Y from 'yjs';
import { WebsocketProvider } from 'y-websocket';

function useYjsDocument(roomName: string) {
  const [doc] = useState(() => new Y.Doc());
  const [connected, setConnected] = useState(false);

  useEffect(() => {
    const provider = new WebsocketProvider(
      'wss://kraken.helixpods.ai',
      roomName,
      doc,
      { params: { token: process.env.KRAKEN_TOKEN } }
    );

    provider.on('status', ({ status }) => {
      setConnected(status === 'connected');
    });

    return () => provider.destroy();
  }, [roomName, doc]);

  return { doc, connected };
}

๐Ÿ“น WebRTC Signaling

Kraken supports WebRTC signaling for peer-to-peer video, audio, and data channels. The signaling protocol runs alongside Yjs sync on the same WebSocket connection.

How It Works

1. Announce - When you connect, announce yourself to get the peer list

2. Discover - Receive list of peers and notifications when peers join/leave

3. Signal - Exchange SDP offers/answers and ICE candidates through Kraken

4. Connect - Establish direct P2P connection for media/data

Signal Message Format

Signaling messages are JSON wrapped in an envelope:

{
  "kraken": "signal",
  "signal": {
    "type": 0,  // SignalMessageType enum
    "from": "peer-uuid",
    "to": "target-peer-uuid",  // optional for broadcasts
    ...
  }
}

Message Types

TypeValueDescription
Announce0Register yourself and get peer list
Offer1Send SDP offer to initiate call
Answer2Send SDP answer to accept call
IceCandidate3Exchange ICE candidates
Hangup4End a call
PeerList5List of all peers (server โ†’ client)
PeerJoined6New peer notification
PeerLeft7Peer disconnected notification

Using @kraken/client

import { KrakenClient } from '@kraken/client';

const client = new KrakenClient({
  url: 'wss://kraken.helixpods.ai',
  token: 'YOUR_TOKEN',
  room: 'my-room',
  name: 'Alice',
});

// Listen for events
client.on((event) => {
  switch (event.type) {
    case 'peers':
      console.log('Peers in room:', event.peers);
      break;
    case 'peer-joined':
      console.log('New peer:', event.peer.name);
      break;
    case 'call-incoming':
      console.log('Incoming call from:', event.from.name);
      break;
    case 'call-established':
      // Attach remote stream to video element
      videoEl.srcObject = event.remoteStream;
      break;
  }
});

await client.connect();

// Start a video call
await client.call('peer-id', { video: true, audio: true });

React Hooks

import { useKraken, useVideoCall } from '@kraken/client/react';

function VideoRoom() {
  const { peers, signaling } = useKraken({
    url: 'wss://kraken.helixpods.ai',
    token: process.env.KRAKEN_TOKEN,
    room: 'video-room',
    name: 'Alice',
  });

  const { localStream, remoteStreams, call, hangup } = useVideoCall({
    signaling,
    video: true,
    audio: true,
  });

  return (
    <div>
      {/* Local video */}
      <video ref={el => el && (el.srcObject = localStream)} autoPlay muted />

      {/* Remote videos */}
      {Array.from(remoteStreams).map(([peerId, stream]) => (
        <video key={peerId} ref={el => el && (el.srcObject = stream)} autoPlay />
      ))}

      {/* Peer list */}
      {peers.map(peer => (
        <button key={peer.peerId} onClick={() => call(peer.peerId)}>
          Call {peer.name}
        </button>
      ))}
    </div>
  );
}

STUN/TURN Configuration

By default, Kraken client uses Google's public STUN servers. For production, configure your own:

const client = new KrakenClient({
  url: 'wss://kraken.helixpods.ai',
  token: 'YOUR_TOKEN',
  room: 'my-room',
  iceServers: [
    { urls: 'stun:stun.l.google.com:19302' },
    {
      urls: 'turn:turn.example.com:3478',
      username: 'user',
      credential: 'pass'
    }
  ]
});

โš ๏ธ Error Codes

CodeMeaningSolution
401UnauthorizedCheck your token
403ForbiddenOrigin not registered
426Upgrade RequiredUse WebSocket, not HTTP

โ† Back to Home ยท GitHub ยท Nexartis