risco-cloud-client
v1.0.0
Published
A fully typed TypeScript client for interfacing with RISCO Cloud API to control RISCO alarm systems
Maintainers
Readme
risco-cloud-client
A fully typed TypeScript client for interfacing with RISCO Cloud API to control RISCO alarm systems
A production-ready TypeScript client library that provides a clean, type-safe interface for managing your RISCO alarm system programmatically. Built with hexagonal architecture principles, featuring comprehensive error handling, automatic session management, and full TypeScript support with complete JSDoc documentation.
Why This Library?
This library was designed from the ground up with production use in mind, focusing on developer experience, type safety, and long-term maintainability:
🛡️ Type Safety First - Full TypeScript support with comprehensive types, ensuring compile-time safety and excellent IDE autocomplete. Catch errors before runtime, not in production.
🏗️ Enterprise Architecture - Built using Hexagonal Architecture (Ports & Adapters), ensuring clean separation of concerns, testability, and the ability to adapt to API changes without breaking your code.
🚀 Production-Ready Features - Automatic session management with expiration tracking, intelligent retry logic, structured error handling, and observability built-in. Perfect for long-running services and production deployments.
📚 Developer Experience - Complete JSDoc documentation, intuitive API design with options-based methods, and comprehensive error types that guide you to solutions.
🔒 Immutability & Safety - Frozen configurations prevent accidental mutations, and the architecture ensures predictable behavior even as the underlying API evolves.
Whether you're building a home automation system, integrating with a smart home platform, or creating a monitoring service, this library provides the reliability and type safety you need for production applications.
Features
- 🔐 Two-Step Authentication - Secure login with username/password and site PIN
- 🏠 Partition Control - Arm (partial/full), disarm, and monitor partitions
- 🚨 Zone Management - Monitor zones and manage bypasses
- 📊 System Status - Get real-time system status and overview
- 📜 Event History - Retrieve alarm events and system history
- 🔄 Auto-retry - Automatic retry logic for failed requests
- 🛡️ Type Safety - Full TypeScript support with comprehensive types
- 🚫 Error Handling - Detailed error types and handling
- 🍪 Session Management - Automatic session handling with expiration tracking and auto-reauthentication
- 🔒 Immutability - Frozen configuration prevents accidental mutations
- 🔄 Site Switching - Switch between multiple sites without recreating the client
- 🏗️ Clean Architecture - Hexagonal architecture with clear separation of concerns
- 📚 Complete JSDoc - Comprehensive documentation with examples
Installation
Using npm:
npm install risco-cloud-clientUsing pnpm:
pnpm add risco-cloud-clientUsing yarn:
yarn add risco-cloud-clientQuick Start
import { RiscoCloudClient } from 'risco-cloud-client';
// Create a client instance
const client = new RiscoCloudClient({
username: '[email protected]',
password: 'your-password',
pin: '1234', // Required: Site PIN
languageId: 1, // Required: Language ID (1 for English)
siteId: 'your-site-id', // Optional but recommended
});
// Authenticate (optional - auto-authenticates on first API call)
await client.authenticate();
// Get partitions
const partitions = await client.getPartitions();
console.log(`System has ${partitions.length} partitions`);
// Get zones
const zones = await client.getZones();
console.log(`System has ${zones.length} zones`);
// Disarm partition
await client.disarm({ partitionId: 0 });
// Partially arm partition (Home mode)
await client.armHome({ partitionId: 0 });
// Fully arm partition (Away mode)
await client.arm({ partitionId: 0 });Configuration
interface RiscoClientConfig {
username: string; // Required: RISCO Cloud username/email
password: string; // Required: RISCO Cloud password
pin: string; // Required: Site PIN
languageId: number; // Required: Language ID (1 for English)
siteId?: string; // Optional: Site ID (recommended, can be changed with switchSite())
baseUrl?: string; // Optional: Defaults to https://www.riscocloud.com/webapi/api
timeout?: number; // Optional: Request timeout in ms (default: 30000)
retryAttempts?: number; // Optional: Retry attempts (default: 3)
retryDelay?: number; // Optional: Retry delay in ms (default: 1000)
logger?: Logger; // Optional: Logger for observability (default: no logging)
}Important: The configuration is immutable (frozen) after client creation. This prevents accidental mutations and ensures predictable behavior. To change the site, use the {@link switchSite} method instead of modifying the config object.
Example with Custom Configuration and Logging
import { RiscoCloudClient, ConsoleLogger, LogLevel } from 'risco-cloud-client';
const client = new RiscoCloudClient({
username: '[email protected]',
password: 'secure-password',
pin: '1234',
languageId: 1, // 1 for English
siteId: 'your-site-id',
timeout: 30000,
retryAttempts: 3,
retryDelay: 1000,
logger: new ConsoleLogger(LogLevel.INFO), // Optional: enable logging (INFO level = default)
// LogLevel options: DEBUG, INFO (default), WARN, ERROR
});Usage Examples
Authentication
// Authenticate (optional - will auto-authenticate on first API call)
await client.authenticate();
// Check authentication status
if (client.isAuthenticated()) {
console.log('Authenticated!');
}
// Switch to a different site
await client.switchSite('new-site-id');
console.log(`Now connected to: ${client.getSiteId()}`);
// Refresh session (useful for long-running processes)
// Sessions expire after ~1 hour - refresh proactively
await client.refreshSession();
// Logout
await client.logout();Arm/Disarm Operations
// Disarm partition
await client.disarm({ partitionId: 0 });
// Partially arm partition (Home/Partial mode)
await client.armHome({ partitionId: 0 });
// Fully arm partition (Away mode)
await client.arm({ partitionId: 0 });Getting System Information
// Get all partitions
const partitions = await client.getPartitions();
partitions.forEach(p => {
console.log(`${p.name}: ${p.state}`);
});
// Get only armed partitions
const armedPartitions = await client.getPartitions({ state: 'ARMED' });
// Get all zones
const zones = await client.getZones();
zones.forEach(z => {
console.log(`${z.name} (${z.type}): ${z.state}`);
});
// Get open zones for partition 1
const openZones = await client.getZones({
partitionId: 1,
state: 'OPEN'
});Events
// Get event history (last 30 days, up to 100 events)
const events = await client.getEvents();
events.forEach(event => {
console.log(`[${event.timestamp}] ${event.type}: ${event.description}`);
});
// Get events from last 24 hours
const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000);
const recentEvents = await client.getEvents({
newerThan: yesterday,
count: 50
});
// Get only alarm events
const alarms = await client.getEvents({ eventType: 'ALARM' });
// Get events for specific partition
const partitionEvents = await client.getEvents({
partitionId: 1,
count: 20
});Error Handling
import {
RiscoCloudClient,
AuthenticationError,
NetworkError,
TimeoutError,
ValidationError,
RiscoCloudError
} from 'risco-cloud-client';
try {
await client.arm({ partitionId: 0 }); // Arm the system
} catch (error) {
if (error instanceof AuthenticationError) {
console.error('Authentication failed:', error.message);
// Handle authentication error
} else if (error instanceof NetworkError) {
console.error('Network error:', error.message);
// Handle network error
} else if (error instanceof TimeoutError) {
console.error('Request timeout:', error.message);
// Handle timeout
} else if (error instanceof ValidationError) {
console.error('Validation error:', error.message);
// Handle validation error
} else if (error instanceof RiscoCloudError) {
console.error('RISCO API error:', error.message, error.code);
// Handle API error
}
}API Reference
Authentication Methods
authenticate(pin?: string): Promise<AuthResponse>
Authenticates with RISCO Cloud using a two-step process:
- Login with username/password
- Site login with site ID and PIN
Parameters:
pin(optional): PIN code. If not provided, uses PIN from config.
Returns: Promise resolving to AuthResponse with site ID and user info.
isAuthenticated(): boolean
Checks if the client is currently authenticated.
Returns: true if authenticated, false otherwise.
logout(): Promise<void>
Logs out and clears authentication state.
System Information Methods
getPartitions(options?: GetPartitionsOptions): Promise<Partition[]>
Retrieves partitions from the control panel with optional filtering.
Parameters:
options(optional): Partition retrieval optionsoptions.state(optional): Filter partitions by state (e.g., 'ARMED', 'DISARMED')
Returns: Promise resolving to array of Partition objects.
Example:
// Get all partitions
const partitions = await client.getPartitions();
// Get only armed partitions
const armed = await client.getPartitions({ state: 'ARMED' });getZones(options?: GetZonesOptions): Promise<Zone[]>
Retrieves zones (sensors/detectors) from the control panel with optional filtering.
Parameters:
options(optional): Zone retrieval optionsoptions.partitionId(optional): Filter zones by partition IDoptions.state(optional): Filter zones by state (e.g., 'OPEN', 'CLOSED', 'ALARM')
Returns: Promise resolving to array of Zone objects.
Example:
// Get all zones
const zones = await client.getZones();
// Get open zones
const openZones = await client.getZones({ state: 'OPEN' });
// Get zones for partition 1
const partitionZones = await client.getZones({ partitionId: 1 });Control Methods
arm(options?: ArmOptions): Promise<void>
Arms the system in Away mode (fully armed). Activates all zones including interior zones.
Parameters:
options(optional): Arm optionsoptions.partitionId(optional): Partition ID to arm. Defaults to 0.
Example:
await client.arm({ partitionId: 0 }); // Fully arm partition 0
await client.arm({ partitionId: 1 }); // Fully arm partition 1armHome(options?: ArmOptions): Promise<void>
Arms the system in Home mode (partially armed). Activates perimeter zones while typically bypassing interior zones.
Parameters:
options(optional): Arm optionsoptions.partitionId(optional): Partition ID to partially arm. Defaults to 0.
Example:
await client.armHome({ partitionId: 0 }); // Partially arm partition 0disarm(options?: DisarmOptions): Promise<void>
Disarms the specified partition.
Parameters:
options(optional): Disarm optionsoptions.partitionId(optional): Partition ID to disarm. Defaults to 0.
Example:
await client.disarm({ partitionId: 0 }); // Disarm partition 0
await client.disarm({ partitionId: 1 }); // Disarm partition 1Event Methods
getEvents(options?: GetEventsOptions): Promise<Event[]>
Retrieves alarm system events and history from the control panel with optional filtering.
Parameters:
options(optional): Event retrieval optionsoptions.newerThan(optional): Date, ISO string, or Unix timestamp to filter events. Defaults to 30 days ago if not specified.options.count(optional): Limit on number of events to return. Defaults to 100.options.partitionId(optional): Filter events by partition IDoptions.eventType(optional): Filter events by type (e.g., "ALARM", "ARM", "UNSET")
Returns: Promise resolving to array of Event objects, ordered by timestamp (newest first).
Example:
// Get all events from last 24 hours
const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000);
const events = await client.getEvents({ newerThan: yesterday, count: 50 });
// Get only alarm events
const alarms = await client.getEvents({ eventType: 'ALARM' });
// Get events for specific partition
const partitionEvents = await client.getEvents({ partitionId: 1, count: 20 });Utility Methods
getSiteId(): string | null
Gets the current site ID from the active session.
Returns: Site ID string if authenticated, null if not authenticated
Example:
const siteId = client.getSiteId();
if (siteId) {
console.log(`Connected to site: ${siteId}`);
}switchSite(siteId: string): Promise<AuthResponse>
Switches the client to a different site and re-authenticates.
The client configuration is immutable, so this method creates a new session with the specified site ID while preserving all other configuration (credentials, logger, etc.). This is the recommended way to change sites without recreating the client.
Parameters:
siteId(required): The site ID to switch to
Returns: Promise resolving to authentication response for the new site
Throws:
AuthenticationError- If authentication fails with the new site
Example:
// Switch to a different site
await client.switchSite('new-site-id');
console.log(`Now connected to: ${client.getSiteId()}`);
// Now all operations will use the new site
const zones = await client.getZones();refreshSession(): Promise<AuthResponse>
Refreshes the current authentication session by re-authenticating with stored credentials.
Use Case: Long-running processes where sessions may expire. Sessions typically expire after 1 hour (sessionId) or ~2.78 hours (accessToken).
Returns: Promise resolving to new authentication response.
Example:
// Proactive refresh in long-running servers
setInterval(async () => {
try {
await client.refreshSession();
console.log('Session refreshed');
} catch (error) {
console.error('Failed to refresh session:', error);
}
}, 50 * 60 * 1000); // Every 50 minutes (before 1 hour expiration)Types
ArmMode
enum ArmMode {
UNSET = 1, // Unset/Disarm
PART_SET = 2, // Part set/Partial arm (Home mode)
FULL_SET = 3, // Full set/Arm (Away mode)
}PartitionState
type PartitionState = 'DISARMED' | 'ARMED' | 'ARMING' | 'DISARMING' | 'ALARM' | 'ENTRY_DELAY';ZoneState
type ZoneState = 'CLOSED' | 'OPEN' | 'TAMPER' | 'ALARM' | 'BYPASSED';ZoneType
type ZoneType = 'PIR' | 'DOOR' | 'WINDOW' | 'GLASS' | 'SMOKE' | 'CO' | 'PANIC' | 'OTHER';See the full type definitions in the source code or refer to the JSDoc documentation in your IDE.
Development
Prerequisites
- Node.js (v18 or higher)
- pnpm (recommended), npm, or yarn
Setup
# Clone the repository
git clone https://github.com/themanjman/risco-cloud-client.git
cd risco-cloud-client
# Install dependencies
pnpm install
# Build the project
pnpm build
# Watch mode for development
pnpm devTesting
# Edit src/example.ts with your credentials
# Then run:
node dist/example.jsArchitecture
This SDK is built using Hexagonal Architecture (Ports & Adapters) principles:
- Domain Layer: Pure business logic with entities, value objects, and ports (interfaces)
- Application Layer: Use cases orchestrating domain behavior
- Infrastructure Layer: Adapters translating between domain and protocol
- Protocol Layer: Reverse-engineered HTTP protocol implementation
- Client Layer: Public API that hides all implementation details
This architecture ensures:
- Protocol changes don't affect domain logic
- Testability without network dependencies
- Clear separation of concerns
- Long-term maintainability
Observability
The SDK includes built-in observability features for debugging and monitoring:
Logging
You can enable logging by providing a logger in the client configuration. The SDK supports dependency injection, allowing you to integrate with any logging system.
Console Logger (Built-in)
import { RiscoCloudClient, ConsoleLogger, LogLevel } from 'risco-cloud-client';
const client = new RiscoCloudClient({
// ... other config
logger: new ConsoleLogger(LogLevel.INFO), // Enable console logging at INFO level
});
// Log level options:
// - LogLevel.DEBUG - All logs (most verbose)
// - LogLevel.INFO - Info, warnings, and errors (default)
// - LogLevel.WARN - Warnings and errors only
// - LogLevel.ERROR - Errors only (least verbose)Custom Logger Integration
You can inject your own logger implementation (Winston, Pino, etc.):
import { Logger, LogContext } from 'risco-cloud-client';
class MyCustomLogger implements Logger {
debug(message: string, context?: LogContext): void {
// Your logging implementation
}
info(message: string, context?: LogContext): void {
// Your logging implementation
}
warn(message: string, context?: LogContext): void {
// Your logging implementation
}
error(message: string, error?: Error, context?: LogContext): void {
// Your logging implementation
}
}
const client = new RiscoCloudClient({
// ... other config
logger: new MyCustomLogger(),
});Environment-Based Log Levels
const logLevel = process.env.NODE_ENV === 'production'
? LogLevel.WARN
: LogLevel.DEBUG;
const client = new RiscoCloudClient({
// ... other config
logger: new ConsoleLogger(logLevel),
});Session Management for Long-Running Processes
For Node.js servers that run continuously, sessions expire after approximately 1 hour. The SDK provides automatic and manual session management:
Automatic Reauthentication (Built-in)
All API operations automatically reauthenticate on 401 errors:
// If session expires, this will automatically refresh and retry
const zones = await client.getZones();Proactive Session Refresh (Recommended)
For best performance, refresh sessions proactively before expiration:
// Refresh every 50 minutes (before 1 hour expiration)
setInterval(async () => {
try {
await client.refreshSession();
console.log('Session refreshed successfully');
} catch (error) {
console.error('Failed to refresh session:', error);
}
}, 50 * 60 * 1000);Session Expiration Details
- Session ID: Expires after ~1 hour
- Access Token: Expires after ~2.78 hours (JWT)
- Both expiration times are tracked automatically from API responses
Error Context
All errors include structured context for debugging:
try {
await client.arm({ partitionId: 0 });
} catch (error) {
if (error instanceof RiscoCloudError) {
console.error('Error:', error.message);
console.error('Code:', error.code);
console.error('Correlation ID:', error.context.correlationId);
console.error('Operation:', error.context.operation);
console.error('Timestamp:', error.context.timestamp);
console.error('Metadata:', error.context.metadata);
}
}Correlation IDs
Every operation generates a unique correlation ID for tracing requests. These are included in logs and error context, allowing you to trace operations across distributed systems:
// All logs for a single operation share the same correlation ID
[INFO] Authenticating with Risco Cloud { correlationId: '1766074892-abc123', operation: 'authenticate' }
[INFO] Authentication successful { correlationId: '1766074892-abc123', operation: 'authenticate', siteId: 'your-site-id' }Important Notes
⚠️ API Reverse-Engineering: The RISCO Cloud API endpoints used in this client are based on reverse-engineering of the official web UI. The actual endpoints may vary and could change without notice. Always test thoroughly before deploying to production.
⚠️ No Official API: RISCO does not provide a public API. This client uses reverse-engineered endpoints that may change without notice.
For detailed protocol documentation, see docs/API.md.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
MIT
Honorable Mentions
This project has been inspired and informed by the work of others in the Risco Cloud reverse-engineering community. We extend our gratitude to the following projects:
- node-risco-client by @mancioshell
- risco-service by @szlaskidaniel
These projects served as valuable references for understanding the RISCO Cloud API behavior and protocol details during development.
Related Projects
- risco-service - Node.js service for RISCO Cloud
- pyrisco - Python library for RISCO Cloud
- node-risco-client - Simple Node.js client for RISCO Cloud
Support
For issues, questions, or contributions, please open an issue on GitHub.
