UNPKG

unifi-protect

Version:

A complete implementation of the UniFi Protect API.

116 lines 6.43 kB
/* Copyright(C) 2019-2025, HJD (https://github.com/hjdhjd). All rights reserved. * * protect-api-events.ts: Our UniFi Protect realtime events API packet utilities. */ import zlib from "node:zlib"; // UniFi Protect events API packet header size, in bytes. const EVENT_PACKET_HEADER_SIZE = 8; // Update realtime API packet types. var ProtectEventPacketType; (function (ProtectEventPacketType) { ProtectEventPacketType[ProtectEventPacketType["HEADER"] = 1] = "HEADER"; ProtectEventPacketType[ProtectEventPacketType["PAYLOAD"] = 2] = "PAYLOAD"; })(ProtectEventPacketType || (ProtectEventPacketType = {})); // Update realtime API payload types. var EventPayloadType; (function (EventPayloadType) { EventPayloadType[EventPayloadType["JSON"] = 1] = "JSON"; EventPayloadType[EventPayloadType["STRING"] = 2] = "STRING"; EventPayloadType[EventPayloadType["BUFFER"] = 3] = "BUFFER"; })(EventPayloadType || (EventPayloadType = {})); /** * @internal * * A packet header is composed of 8 bytes in this order: * * | Byte Offset | Description | Bits | Values | * |-------------|----------------|------|-----------------------------------------------------------------------| * | 0 | Packet Type | 8 | 1 - header frame, 2 - payload frame. | * | 1 | Payload Format | 8 | 1 - JSON object, 2 - UTF8-encoded string, 3 - Node Buffer. | * | 2 | Deflated | 8 | 0 - uncompressed, 1 - compressed / deflated (zlib-based compression). | * | 3 | Unknown | 8 | Always 0. Possibly reserved for future use by Ubiquiti? | * | 4-7 | Payload Size: | 32 | Size of payload in network-byte order (big endian). | */ var ProtectEventPacketHeader; (function (ProtectEventPacketHeader) { ProtectEventPacketHeader[ProtectEventPacketHeader["TYPE"] = 0] = "TYPE"; ProtectEventPacketHeader[ProtectEventPacketHeader["PAYLOAD_FORMAT"] = 1] = "PAYLOAD_FORMAT"; ProtectEventPacketHeader[ProtectEventPacketHeader["DEFLATED"] = 2] = "DEFLATED"; ProtectEventPacketHeader[ProtectEventPacketHeader["UNKNOWN"] = 3] = "UNKNOWN"; ProtectEventPacketHeader[ProtectEventPacketHeader["PAYLOAD_SIZE"] = 4] = "PAYLOAD_SIZE"; })(ProtectEventPacketHeader || (ProtectEventPacketHeader = {})); /** * UniFi Protect event utility class that provides functions for decoding realtime event API packet frames. */ export class ProtectApiEvents { /** @internal */ constructor() { } /** * Decode a UniFi Protect event packet. * * @param log - Logging functions to use. * @param packet - Input packet to decode. * * @remarks A UniFi Protect event packet is an encoded representation of state updates that occur in a UniFi Protect controller. This utility function takes an * encoded packet as an input, and decodes it into an event header and payload that can be acted upon. An example of it's use is in {@link ProtectApi} where, once * successfully logged into the Protect controller, events are generated automatically and can be accessed by listening to `message` events emitted by * {@link ProtectApi}. */ static decodePacket(log, packet) { // What we need to do here is to split this packet into the header and payload, and decode them. let dataOffset; try { // The fourth byte holds our payload size. When you add the payload size to our header frame size, you get the location of the // data header frame. dataOffset = packet.readUInt32BE(ProtectEventPacketHeader.PAYLOAD_SIZE) + EVENT_PACKET_HEADER_SIZE; // Validate our packet size, just in case we have more or less data than we expect. If we do, we're done for now. if (packet.length !== (dataOffset + EVENT_PACKET_HEADER_SIZE + packet.readUInt32BE(dataOffset + ProtectEventPacketHeader.PAYLOAD_SIZE))) { throw new Error("Packet length doesn't match header information."); } } catch (error) { log.error("Realtime events API: error decoding update packet: %s.", error); return null; } // Decode the action and payload frames now that we know where everything is. const headerFrame = this.decodeFrame(log, packet.slice(0, dataOffset), ProtectEventPacketType.HEADER); const payloadFrame = this.decodeFrame(log, packet.slice(dataOffset), ProtectEventPacketType.PAYLOAD); if (!headerFrame || !payloadFrame) { return null; } return ({ header: headerFrame, payload: payloadFrame }); } // Decode a frame, composed of a header and payload, received through the update events API. static decodeFrame(log, packet, packetType) { // Read the packet frame type. const frameType = packet.readUInt8(ProtectEventPacketHeader.TYPE); // This isn't the frame type we were expecting - we're done. if (packetType !== frameType) { return null; } // Read the payload format. const payloadFormat = packet.readUInt8(ProtectEventPacketHeader.PAYLOAD_FORMAT); // Check to see if we're compressed or not, and inflate if needed after skipping past the 8-byte header. const payload = packet.readUInt8(ProtectEventPacketHeader.DEFLATED) ? zlib.inflateSync(packet.slice(EVENT_PACKET_HEADER_SIZE)) : packet.slice(EVENT_PACKET_HEADER_SIZE); // If it's a header, it can only have one format. if (frameType === ProtectEventPacketType.HEADER) { return (payloadFormat === EventPayloadType.JSON) ? JSON.parse(payload.toString()) : null; } // Process the payload format accordingly. switch (payloadFormat) { case EventPayloadType.JSON: // If it's data payload, it can be anything. return JSON.parse(payload.toString()); case EventPayloadType.STRING: return payload.toString("utf8"); case EventPayloadType.BUFFER: return payload; default: log.error("Unknown payload packet type received in the realtime events API: %s.", payloadFormat); return null; } } } //# sourceMappingURL=protect-api-events.js.map