UNPKG

unifi-protect

Version:

A complete implementation of the UniFi Protect API.

360 lines (359 loc) 17.1 kB
/** * A complete implementation of the UniFi Protect API, including access to the events, livestream data (not just RTSP), and websockets endpoints. * * The UniFi Protect API is largely undocumented and has been reverse engineered mostly through the Protect native web interface as well as trial and error. This * implementation provides a high-performance, event-driven interface into the Protect API, allowing you to access all of Protect's rich capabilities. * * @module ProtectApi */ import { RequestOptions, Response } from "@adobe/fetch"; import { Nullable, ProtectCameraConfig, ProtectCameraConfigInterface, ProtectCameraConfigPayload, ProtectChimeConfig, ProtectChimeConfigPayload, ProtectLightConfig, ProtectLightConfigPayload, ProtectNvrBootstrap, ProtectNvrConfig, ProtectNvrConfigPayload, ProtectSensorConfig, ProtectSensorConfigPayload, ProtectViewerConfig, ProtectViewerConfigPayload } from "./protect-types.js"; import { EventEmitter } from "node:events"; import { ProtectLivestream } from "./protect-api-livestream.js"; import { ProtectLogging } from "./protect-logging.js"; /** * Define our known Protect device types. */ export type ProtectKnownDeviceTypes = ProtectCameraConfig | ProtectChimeConfig | ProtectLightConfig | ProtectNvrConfig | ProtectSensorConfig | ProtectViewerConfig; /** * Define our known Protect device payload types. */ export type ProtectKnownDevicePayloads = ProtectCameraConfigPayload | ProtectChimeConfigPayload | ProtectLightConfigPayload | ProtectNvrConfigPayload | ProtectSensorConfigPayload | ProtectViewerConfigPayload; /** * This class provides an event-driven API to access the UniFi Protect API. Here's how to quickly get up and running with this library once you've instantiated the class: * * 1. {@link login | Login} to the UniFi Protect controller and acquire security credentials for further calls to the API. * * 2. Retrieve the current configuration and state of the Protect controller by calling the {@link bootstrap} endpoint. This contains everything you would want to know * about this particular UniFi Protect controller, including enumerating all the devices it knows about. * * 3. Listen for `message` events emitted by {@link ProtectApi} containing all Protect controller events, in realtime. They are delivered as * {@link ProtectApiEvents.ProtectEventPacket | ProtectEventPacket} packets, containing the event-specific details. * * Those are the basics that gets us up and running. */ export declare class ProtectApi extends EventEmitter { private _bootstrap; private _eventsWs; private apiErrorCount; private apiLastSuccess; private fetch; private headers; private _isAdminUser; private log; private nvrAddress; private password; private username; /** * Create an instance of the UniFi Protect API. * * @param log - Logging functions to use. * * @defaultValue `none` - Logging will be done to stdout and stderr. * * @category Constructor */ constructor(log?: ProtectLogging); /** * Execute a login attempt to the UniFi Protect API. * * @param nvrAddress - Address of the UniFi Protect controller, expressed as an FQDN or IP address. * @param username - Username to use when logging into the controller. * @param password - Password to use when logging into the controller. * * @returns Returns a promise that will resolve to `true` if successful and `false` otherwise. * * @remarks A `login` event will be emitted each time this method is called, with the result of the attempt as an argument. If there are any existing logins from prior * calls to login, they will be terminated. * * @example * Login to the Protect controller. You can selectively choose to either `await` the promise that is returned by `login`, or subscribe to the `login` event. * * ```ts * import { ProtectApi } from "unifi-protect"; * * // Create a new Protect API instance. * const ufp = new ProtectApi(); * * // Set a listener to wait for the login event to occur. * ufp.once("login", (successfulLogin: boolean) => { * * // Indicate if we are successful. * if(successfulLogin) { * * console.log("Logged in successfully."); * process.exit(0); * } * }); * * // Login to the Protect controller. * if(!(await ufp.login("protect-controller.local", "username", "password"))) { * * console.log("Invalid login credentials."); * process.exit(0); * }; * ``` * * @category Authentication */ login(nvrAddress: string, username: string, password: string): Promise<boolean>; private loginController; private bootstrapController; private launchEventsWs; /** * Retrieve the bootstrap JSON from a UniFi Protect controller. * * @returns Returns a promise that will resolve to `true` if successful and `false` otherwise. * * @remarks A `bootstrap` event will be emitted each time this method is successfully called, with the {@link ProtectNvrBootstrap} JSON as an argument. * * @example * Retrieve the bootstrap JSON. You can selectively choose to either `await` the promise that is returned by `getBootstrap`, or subscribe to the `bootstrap` event. * * ```ts * import { ProtectApi, ProtectNvrBootstrap } from "unifi-protect"; * import util from "node:util"; * * // Create a new Protect API instance. * const ufp = new ProtectApi(); * * // Set a listener to wait for the bootstrap event to occur. * ufp.once("bootstrap", (bootstrapJSON: ProtectNvrBootstrap) => { * * // Once we've bootstrapped the Protect controller, output the bootstrap JSON and we're done. * process.stdout.write(util.inspect(bootstrapJSON, { colors: true, depth: null, sorted: true }) + "\n", () => process.exit(0)); * }); * * // Login to the Protect controller. * if(!(await ufp.login("protect-controller.local", "username", "password"))) { * * console.log("Invalid login credentials."); * process.exit(0); * }; * * // Bootstrap the controller. It will emit a message once it's received the bootstrap JSON, or you can alternatively wait for the promise to resolve. * if(!(await ufp.getBootstrap())) { * * console.log("Unable to bootstrap the Protect controller."); * process.exit(0); * } * ``` * * Alternatively, you can access the bootstrap JSON directly through the {@link bootstrap} accessor: * * ```ts * import { ProtectApi } from "unifi-protect"; * import util from "node:util"; * * // Create a new Protect API instance. * const ufp = new ProtectApi(); * * // Login to the Protect controller. * if(!(await ufp.login("protect-controller.local", "username", "password"))) { * * console.log("Invalid login credentials."); * process.exit(0); * }; * * // Bootstrap the controller. * if(!(await ufp.getBootstrap())) { * * console.log("Unable to bootstrap the Protect controller."); * process.exit(0); * } * * // Once we've bootstrapped the Protect controller, access the bootstrap JSON through the bootstrap accessor and we're done. * process.stdout.write(util.inspect(ufp.bootstrap, { colors: true, depth: null, sorted: true }) + "\n", () => process.exit(0)); * ``` * * @category API Access */ getBootstrap(): Promise<boolean>; private checkAdminUserStatus; /** * Retrieve a snapshot image from a Protect camera. * * @param device - Protect device. * @param options - Parameters to pass on for the snapshot request. * * @returns Returns a promise that will resolve to a Buffer containing the JPEG image snapshot if successful, and `null` otherwise. * * @remarks The `options` object for snapshot parameters accepts the following properties, all of which are optional: * * | Property | Description | * |-------------------|------------------------------------------------------------------------------------------------------------| * | height | The image height to request. Defaults selected by the Protect controller, based on the camera resolution. | * | width | The image width to request. Defaults selected by the Protect controller, based on the camera resolution. | * | usePackageCamera | Retriver a snapshot fron the package camera rather than the primary camera lens. Defaults to `false`. | * * @category API Access */ getSnapshot(device: ProtectCameraConfig, options?: Partial<{ width: number; height: number; usePackageCamera: boolean; }>): Promise<Nullable<Buffer>>; /** * Update a Protect device's configuration on the UniFi Protect controller. * * @typeParam DeviceType - Generic for any known Protect device type. * * @param device - Protect device. * @param payload - Device configuration payload to upload, usually a subset of the device-specific configuration JSON. * * @returns Returns a promise that will resolve to the updated device-specific configuration JSON if successful, and `null` otherwise. * * @remarks Use this method to change the configuration of a given Protect device or controller. It requires the credentials used to login to the Protect API * to have administrative privileges for most settings. * * @category API Access */ updateDevice<DeviceType extends ProtectKnownDeviceTypes>(device: DeviceType, payload: ProtectKnownDevicePayloads): Promise<Nullable<DeviceType>>; private updateCameraChannels; /** * Utility method that enables all RTSP channels on a given Protect camera. * * @param device - Protect camera to modify. * * @returns Returns a promise that will resolve to the updated {@link ProtectCameraConfig} if successful, and `null` otherwise. * * @category Utilities */ enableRtsp(device: ProtectCameraConfigInterface): Promise<Nullable<ProtectCameraConfig>>; /** * Utility method that generates a nicely formatted device information string. * * @param device - Protect device. * @param name - Optional name for the device. Defaults to the device type (e.g. `G4 Pro`). * @param deviceInfo - Optionally specify whether or not to include the IP address and MAC address in the returned string. Defaults to `false`. * * @returns Returns the Protect device name in the following format: <code>*Protect device name* [*Protect device type*] (address: *IP address* * mac: *MAC address*)</code>. * * @remarks The example above assumed the `deviceInfo` parameter is set to `true`. * * @category Utilities */ getDeviceName(device: ProtectKnownDeviceTypes, name?: string, deviceInfo?: boolean): string; /** * Utility method that generates a combined, nicely formatted device and NVR string. * * @param device - Protect device. * * @returns Returns the Protect device name in the following format: * <code>*Protect controller name* [*Protect controller type*] *Protect device name* [*Protect device type*]</code>. * * @category Utilities */ getFullName(device: ProtectKnownDeviceTypes): string; /** * Terminate any open connection to the UniFi Protect API. * * @category Utilities */ reset(): void; /** * Clear the login credentials and terminate any open connection to the UniFi Protect API. * * @category Authentication */ logout(): void; private canModifyCamera; /** * Return a websocket API endpoint for the requested endpoint type. * * @param endpoint - Requested endpoint type. Valid types are `livestream` and `talkback`. * @param params - Parameters to pass on for the endpoint request. * * @returns Returns a promise that will resolve to a URL to the requested endpoint if successful, and `null` otherwise. * * @remarks Valid API endpoints are `livestream` and `talkback`. * * - The `livestream` endpoint will return a URL to a websocket that provides an encoded livestream from a given camera. **Do not access this endpoint directly, use * {@link createLivestream} instead.** Accessing the livestream endpoint directly is not directly useful without additional manipulation, which, unless you have * a need for, you should avoid dealing with and use the {@link ProtectLivestream} API instead that provides you direct access to the livestream as an H.264 fMP4. * - The `talkback` endpoint creates a talkback connection to a Protect camera that contains a speaker (e.g. Protect doorbells). * The returned websocket accepts an AAC-encoded ADTS stream. The only valid parameter is `camera`, containing the ID of the Protect camera you want to connect to. * * @category API Access */ getWsEndpoint(endpoint: "livestream" | "talkback", params?: URLSearchParams): Promise<Nullable<string>>; private _getWsEndpoint; /** * Execute an HTTP fetch request to the Protect controller. * * @param url - Complete URL to execute **without** any additional parameters you want to pass (e.g. https://unvr.local/proxy/protect/cameras/someid/snapshot). * @param options - Parameters to pass on for the endpoint request. * @param logErrors - Log errors that aren't already accounted for and handled, rather than failing silently. Defaults to `true`. * * @returns Returns a promise that will resolve to a Response object successful, and `null` otherwise. * * @remarks This method should be used when direct access to the Protect controller is needed, or when this library doesn't have a needed method to access * controller capabilities. `options` must be a * [Fetch API compatible](https://developer.mozilla.org/en-US/docs/Web/API/Request/Request#options) request options object. * * @category API Access */ retrieve(url: string, options?: RequestOptions, logErrors?: boolean): Promise<Nullable<Response>>; private _retrieve; private logRetry; /** * Return a new instance of the Protect livestream API. * * @returns Returns a new livestream API object. * * @remarks This method should be used to create a new livestream API object. It allows you to create access livestreams of individual cameras and interact * directly with the H.264 fMP4 streams for a given camera. * * @category API Access */ createLivestream(): ProtectLivestream; /** * Return an API endpoint for the requested endpoint type. * * @param endpoint - Requested endpoint type. * * @returns Returns a URL to the requested endpoint if successful, and an empty string otherwise. * * @remarks Valid API endpoints are `bootstrap`, `camera`, `chime`, `light`, `login`, `nvr`, `self`, `sensor`, `websocket` and `viewer`. * * @category API Access */ getApiEndpoint(endpoint: string): string; /** * Access the Protect controller bootstrap JSON. * * @returns Returns the bootstrap JSON if the Protect controller has been bootstrapped, `null` otherwise. * * @remarks A call to {@link getBootstrap} is required before calling this getter. Otherwise, it will return `null`. Put another way, you need to execute a bootstrap * request to the Protect controller before accessing the bootstrap JSON. * * @category API Access */ get bootstrap(): Nullable<ProtectNvrBootstrap>; /** * Utility method that returns whether the credentials that were used to login to the Protect controller have administrative privileges or not. * * @returns Returns `true` if the logged in user has administrative privileges, `false` otherwise. * * @category Utilities */ get isAdminUser(): boolean; /** * Utility method that returns whether our connection to the Protect controller is currently throttled or not. * * @returns Returns `true` if the API has returned too many errors and is now throttled for a period of time, `false` otherwise. * * @category Utilities */ get isThrottled(): boolean; /** * Utility method that returns a nicely formatted version of the Protect controller name. * * @returns Returns the Protect controller name in the following format: * <code>*Protect controller name* [*Protect controller type*]</code>. * * @category Utilities */ get name(): string; }