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
HELLOmessage 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â
| Option | Type | Required | Description |
|---|---|---|---|
url | string | Yes | Server address (WebSocket or HTTPS). |
tenantId | string | Yes | Your organization's ID. |
deviceId | string | Yes | Your device's unique ID. |
signingKey | string | Yes | Your Ed25519 private key (64 hex chars). |
clientId | string | Yes | A unique name for this app instance. |
serverPublicKey | string | Yes | Cloud's public key for verifying inbound messages. |
previousServerPublicKey | string | No | Previous cloud key (used during key rotation). |
crypto | CryptoProvider | No | Custom crypto (auto-detected: WebCrypto or Node crypto). |
replayProtection | boolean | No | Enable replay attack prevention (default: true in Node). |
Eventsâ
| Event | Payload | When it fires |
|---|---|---|
connect | void | Client 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();