Cloud Device Connection
The @larva.io/clouddevice library provides a WebSocket-based connection to Fentrica edge devices through a cloud broker proxy. This enables real-time, bidirectional communication between your application and the physical devices in a building.
Installation
npm install --save @larva.io/clouddevice
Quick Start
import { Device } from '@larva.io/clouddevice';
const device = new Device('deviceId', 'unitId', 'accessToken', {
server: 'wss://broker.fentrica.com',
timeout: 8000
});
device.open().then(() => {
return device.getUINodes();
}).then(nodes => {
console.log(nodes);
return device.close();
});
Connection Flow
- Create Device Instance — Instantiate a Device with the device ID, unit ID, and access token from the Service Connection API response.
- Open WebSocket — Call
device.open()to establish a persistent WebSocket connection through the Fentrica broker proxy. - Fetch UI Nodes — Call
device.getUINodes()to retrieve the device's UI configuration — the list of controllable components (switches, sliders, sensors, etc.). - Listen for Events — Register event listeners for broadcast messages, connection state changes, and errors.
- Render & Interact — Pass the UI node configuration to
@larva.io/webcomponentsfor rendering. Handle user interactions via output and request events.
API Reference
Constructor
new Device(deviceId: string, unitId: string, token: string | (() => string | Promise<string>), options?: Options)
| Parameter | Type | Description |
|---|---|---|
deviceId | string | Device ID from the Service Connection API |
unitId | string | Unit ID from the Service Connection API |
token | string | () => string | Promise<string> | JWT access token, or a function that returns one (sync or async). See below. |
options.server | string | WebSocket broker URL (wss://broker.fentrica.com) |
options.timeout | number | Connection timeout in milliseconds (default: 8000) |
Use a token function, not a static string. JWT tokens are short-lived. If you pass a static token string, the connection will fail after the token expires. Pass a function that returns the current valid token so the library can re-authenticate automatically.
// Bad — token will expire
const device = new Device(deviceId, unitId, 'eyJhbG...', options);
// Good — sync function that returns a fresh token
const device = new Device(deviceId, unitId, () => authStore.getAccessToken(), options);
// Good — async function (e.g. refresh token flow)
const device = new Device(deviceId, unitId, () => authService.getValidToken(), options);
Methods
| Method | Returns | Description |
|---|---|---|
open() | Promise<void> | Opens the WebSocket connection |
close() | Promise<void> | Closes the connection |
getUINodes() | Promise<UINode[]> | Fetches the device UI configuration |
request(topic, data?) | Promise<any> | Sends an RPC request to the device |
handleNodeOutput(event) | Promise<void> | Handles output events from web components |
handleNodeRequest(event) | Promise<void> | Handles request events from web components |
Events
| Event | Description |
|---|---|
open | WebSocket connection established |
closed | Connection closed |
broadcast | Device sent a broadcast message (e.g. sensor value update) |
error | Connection or communication error |
device.addEventListener('broadcast', (event) => {
console.log('Device broadcast:', event.detail);
});
device.addEventListener('closed', () => {
console.log('Connection closed');
});
device.addEventListener('error', (event) => {
console.error('Device error:', event.detail);
});
UI Node Structure
The getUINodes() method returns an array of UI node objects that describe the device's controllable components:
interface UINode {
node: {
id: string; // Unique node identifier
type: string; // Component type (e.g. "lar-switch", "lar-slider")
};
ui: {
name: string; // Display name
icon?: string; // Icon identifier
category?: { // Grouping category
name: string;
icon?: string;
};
room?: { // Room assignment
name: string;
icon?: string;
};
favorite: boolean; // User favorite flag
visible: boolean; // Visibility flag
linkednodes?: string[]; // Nested sub-node IDs
};
}
The node.type field corresponds directly to the web component tag name used by @larva.io/webcomponents. Pass this configuration to the web components to render the device UI.
Connecting Multiple Devices
A single unit (smart space) may have multiple devices. Connect to each device from the API response:
import { Device } from '@larva.io/clouddevice';
// unitDevices from the Service Connection API response
const connections = unitDevices.devices.map(
(d) => new Device(d.id, unitDevices.id, accessToken, {
server: 'wss://broker.fentrica.com',
timeout: 8000
})
);
// Open all connections
await Promise.all(connections.map((c) => c.open()));
// Fetch UI nodes from all connections
const allNodes = await Promise.all(
connections.map(async (c) => {
const nodes = await c.getUINodes();
return nodes.map((n) => ({ ...n, connection: c }));
})
);
const nodeList = allNodes.flat();
Keep a reference to the connection alongside each node. When rendering web components, the @output and @request events must be routed back to the correct device connection.
Connection Lifecycle Management
WebSocket connections must be closed when the user leaves the screen or the app goes to the background. Failing to do so wastes resources on the broker and edge device, and can lead to stale connections that silently stop receiving data. When the user returns, re-create all connections from scratch.
This applies to all platforms — browser tabs, mobile web views, and native app shells like Capacitor or Cordova.
Browser (Page Visibility API)
import { Device } from '@larva.io/clouddevice';
let connections: Device[] = [];
async function openConnections(devices: { id: string }[], unitId: string, token: string) {
connections = devices.map(
(d) => new Device(d.id, unitId, token, {
server: 'wss://broker.fentrica.com',
timeout: 8000
})
);
await Promise.all(connections.map((c) => c.open()));
return connections;
}
async function closeConnections() {
await Promise.all(connections.map((c) => c.close()));
connections = [];
}
// Close when tab becomes hidden, re-create when visible again
document.addEventListener('visibilitychange', async () => {
if (document.visibilityState === 'hidden') {
await closeConnections();
} else {
// Re-create connections with fresh state
await openConnections(devices, unitId, token);
}
});
Capacitor / Cordova (Native App)
For native app shells, use the platform's app state listener instead of the Page Visibility API:
import { App } from '@capacitor/app';
import { Device } from '@larva.io/clouddevice';
let connections: Device[] = [];
App.addListener('appStateChange', async ({ isActive }) => {
if (!isActive) {
// App moved to background — close all connections
await Promise.all(connections.map((c) => c.close()));
connections = [];
} else {
// App returned to foreground — re-create connections
connections = await openConnections(devices, unitId, token);
}
});
Vue / React Component Cleanup
Always close connections when the component unmounts:
// Vue 3
onUnmounted(async () => {
await Promise.all(connections.map((c) => c.close()));
});
// React
useEffect(() => {
return () => {
connections.forEach((c) => c.close());
};
}, []);
Next Steps
- Web Components — Render the device UI in your application