npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

risco-cloud-client

v1.0.0

Published

A fully typed TypeScript client for interfacing with RISCO Cloud API to control RISCO alarm systems

Readme

risco-cloud-client

A fully typed TypeScript client for interfacing with RISCO Cloud API to control RISCO alarm systems

npm version License: MIT

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-client

Using pnpm:

pnpm add risco-cloud-client

Using yarn:

yarn add risco-cloud-client

Quick 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:

  1. Login with username/password
  2. 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 options
    • options.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 options
    • options.partitionId (optional): Filter zones by partition ID
    • options.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 options
    • options.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 1

armHome(options?: ArmOptions): Promise<void>

Arms the system in Home mode (partially armed). Activates perimeter zones while typically bypassing interior zones.

Parameters:

  • options (optional): Arm options
    • options.partitionId (optional): Partition ID to partially arm. Defaults to 0.

Example:

await client.armHome({ partitionId: 0 }); // Partially arm partition 0

disarm(options?: DisarmOptions): Promise<void>

Disarms the specified partition.

Parameters:

  • options (optional): Disarm options
    • options.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 1

Event Methods

getEvents(options?: GetEventsOptions): Promise<Event[]>

Retrieves alarm system events and history from the control panel with optional filtering.

Parameters:

  • options (optional): Event retrieval options
    • options.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 ID
    • options.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 dev

Testing

# Edit src/example.ts with your credentials
# Then run:
node dist/example.js

Architecture

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:

These projects served as valuable references for understanding the RISCO Cloud API behavior and protocol details during development.

Related Projects

Support

For issues, questions, or contributions, please open an issue on GitHub.