Skip to main content

hxtp-js (JavaScript / TypeScript SDK)

The hxtp-js library lets your web apps and backend services talk to Hestia Labs devices using the HxTP/3.1 protocol.

Installation​

bun add hxtp-js

Quick Start​

import { HXTPClient } from 'hxtp-js';

const client = new HXTPClient({
url: 'https://api.hestialabs.in/api/v1',
tenantId: 'your-tenant-id',
deviceId: 'your-device-id',
signingKey: 'your-private-key-hex', // Ed25519 private key
clientId: 'my-app-unique-id',
serverPublicKey: 'cloud-public-key-hex', // for verifying cloud messages
});

client.on('connect', () => console.log('Connected and ACTIVE!'));
client.on('message', (msg) => console.log('Message:', msg));
client.on('error', (err) => console.error('Error:', err));

await client.connect();
await client.sendCommand({
action: 'toggle_led',
params: { brightness: 100 },
});
await client.disconnect();

How It Works​

The Lifecycle​

Every device goes through a simple introduction process:

IDLE → HELLO_SENT (device says hello)
→ ACTIVE (cloud says hello back)
→ DISCONNECTED (if connection drops)
  • connect() sends a signed HELLO message with your public key.
  • The cloud replies with HELLO_ACK — now the client is ACTIVE.
  • While ACTIVE, you can send commands and receive messages.
  • If the connection drops, the client automatically tries to reconnect and re-do the handshake.

Sending Commands​

Commands are Ed25519 signed — every message carries a digital signature that proves it came from you.

const response = await client.sendCommand({
action: 'set_level',
params: { brightness: 75 },
deviceId: 'target-device-id', // optional: defaults to config.deviceId
});

Receiving Messages​

client.on('message', (event) => {
console.log('Raw:', event.raw);
console.log('Parsed:', event.parsed);
console.log('Timestamp:', event.timestamp);
});

Client Configuration​

OptionTypeRequiredDescription
urlstringYesServer address (WebSocket or HTTPS).
tenantIdstringYesYour organization's ID.
deviceIdstringYesYour device's unique ID.
signingKeystringYesYour Ed25519 private key (64 hex chars).
clientIdstringYesA unique name for this app instance.
serverPublicKeystringYesCloud's public key for verifying inbound messages.
previousServerPublicKeystringNoPrevious cloud key (used during key rotation).
cryptoCryptoProviderNoCustom crypto (auto-detected: WebCrypto or Node crypto).
replayProtectionbooleanNoEnable replay attack prevention (default: true in Node).

Events​

EventPayloadWhen it fires
connectvoidClient becomes ACTIVE (HELLO_ACK received).
disconnect{ code, reason }Connection lost.
message{ raw, parsed, timestamp }A valid, verified message arrives.
error{ code, message, fatal }Something went wrong.
reconnecting{ attempt, delayMs }Auto-reconnect in progress.

Low-Level Protocol Utilities​

Build a Canonical String​

import { pipeCanonical } from 'hxtp-js';

const canonical = pipeCanonical({
version: 'HxTP/3.1',
device_id: 'device-uuid',
tenant_id: 'tenant-uuid',
client_id: 'client-uuid',
message_id: 'msg-uuid',
request_id: 'req-uuid',
sequence_number: 1,
timestamp: Date.now(),
nonce: 'unique-nonce',
message_type: 'command',
payload_hash: 'sha256-of-params',
});

Sign and Verify​

import { signMessage, verifySignature } from 'hxtp-js';

// Sign
const signature = await signMessage(crypto, privateKeyHex, message);

// Verify
const isValid = await verifySignature(crypto, publicKeyHex, message, signature);

Validate an Inbound Message​

import { validateMessage } from 'hxtp-js';

const result = await validateMessage(parsedMsg, {
crypto,
activePublicKey: cloudPublicKey,
nonceCache,
});

Full Example​

import { HXTPClient } from 'hxtp-js';

async function main() {
const client = new HXTPClient({
url: 'wss://api.hestialabs.in/ws',
tenantId: process.env.TENANT_ID!,
deviceId: process.env.DEVICE_ID!,
signingKey: process.env.SIGNING_KEY!,
clientId: 'my-backend',
serverPublicKey: process.env.CLOUD_PUBLIC_KEY!,
});

client.on('connect', () => console.log('ACTIVE'));
client.on('message', (msg) => console.log('Received:', msg.parsed.action));
client.on('error', (err) => console.error(err));

await client.connect();

// All commands are Ed25519 signed automatically
await client.sendCommand({
action: 'toggle_led',
params: { value: true },
});

process.on('SIGINT', () => client.disconnect());
}

main();