Files
unifi/ts/classes.client.ts

277 lines
6.6 KiB
TypeScript

import type { UnifiController } from './classes.unifi-controller.js';
import type { INetworkClient } from './interfaces/index.js';
/**
* Represents a connected network client
*/
export class UnifiClient implements INetworkClient {
/** Reference to parent controller */
private controller?: UnifiController;
/** Site ID for API calls */
private siteId?: string;
// INetworkClient properties
public _id: string;
public mac: string;
public site_id: string;
public is_guest?: boolean;
public is_wired: boolean;
public first_seen?: number;
public last_seen?: number;
public hostname?: string;
public name?: string;
public ip?: string;
public network_id?: string;
public uplink_mac?: string;
public ap_name?: string;
public essid?: string;
public bssid?: string;
public channel?: number;
public radio_proto?: string;
public signal?: number;
public tx_rate?: number;
public rx_rate?: number;
public tx_bytes?: number;
public rx_bytes?: number;
public tx_packets?: number;
public rx_packets?: number;
public sw_port?: number;
public usergroup_id?: string;
public oui?: string;
public noted?: boolean;
public user_id?: string;
public fingerprint_source?: number;
public dev_cat?: number;
public dev_family?: number;
public dev_vendor?: number;
public dev_id?: number;
public os_name?: number;
public satisfaction?: number;
public anomalies?: number;
constructor() {
this._id = '';
this.mac = '';
this.site_id = '';
this.is_wired = false;
}
/**
* Create a client instance from API response object
*/
public static createFromApiObject(
apiObject: INetworkClient,
controller?: UnifiController,
siteId?: string
): UnifiClient {
const client = new UnifiClient();
Object.assign(client, apiObject);
client.controller = controller;
client.siteId = siteId || apiObject.site_id;
return client;
}
/**
* Get the raw API object representation
*/
public toApiObject(): INetworkClient {
return {
_id: this._id,
mac: this.mac,
site_id: this.site_id,
is_guest: this.is_guest,
is_wired: this.is_wired,
first_seen: this.first_seen,
last_seen: this.last_seen,
hostname: this.hostname,
name: this.name,
ip: this.ip,
network_id: this.network_id,
uplink_mac: this.uplink_mac,
ap_name: this.ap_name,
essid: this.essid,
bssid: this.bssid,
channel: this.channel,
radio_proto: this.radio_proto,
signal: this.signal,
tx_rate: this.tx_rate,
rx_rate: this.rx_rate,
tx_bytes: this.tx_bytes,
rx_bytes: this.rx_bytes,
tx_packets: this.tx_packets,
rx_packets: this.rx_packets,
sw_port: this.sw_port,
usergroup_id: this.usergroup_id,
oui: this.oui,
noted: this.noted,
user_id: this.user_id,
fingerprint_source: this.fingerprint_source,
dev_cat: this.dev_cat,
dev_family: this.dev_family,
dev_vendor: this.dev_vendor,
dev_id: this.dev_id,
os_name: this.os_name,
satisfaction: this.satisfaction,
anomalies: this.anomalies,
};
}
/**
* Get display name (name, hostname, or MAC)
*/
public getDisplayName(): string {
return this.name || this.hostname || this.mac;
}
/**
* Check if client is wireless
*/
public isWireless(): boolean {
return !this.is_wired;
}
/**
* Check if client is a guest
*/
public isGuest(): boolean {
return this.is_guest === true;
}
/**
* Get connection type string
*/
public getConnectionType(): string {
if (this.is_wired) {
return `Wired (Port ${this.sw_port || 'unknown'})`;
}
return `Wireless (${this.essid || 'unknown'})`;
}
/**
* Get signal strength description
*/
public getSignalQuality(): string {
if (this.is_wired) return 'N/A';
if (!this.signal) return 'Unknown';
// Signal is typically in dBm, with higher (less negative) being better
const signal = this.signal;
if (signal >= -50) return 'Excellent';
if (signal >= -60) return 'Good';
if (signal >= -70) return 'Fair';
if (signal >= -80) return 'Poor';
return 'Very Poor';
}
/**
* Block this client from the network
*/
public async block(): Promise<void> {
if (!this.controller || !this.siteId) {
throw new Error('Cannot block client: no controller reference');
}
await this.controller.request(
'POST',
`/api/s/${this.siteId}/cmd/stamgr`,
{
cmd: 'block-sta',
mac: this.mac,
}
);
}
/**
* Unblock this client
*/
public async unblock(): Promise<void> {
if (!this.controller || !this.siteId) {
throw new Error('Cannot unblock client: no controller reference');
}
await this.controller.request(
'POST',
`/api/s/${this.siteId}/cmd/stamgr`,
{
cmd: 'unblock-sta',
mac: this.mac,
}
);
}
/**
* Reconnect (kick) this client
*/
public async reconnect(): Promise<void> {
if (!this.controller || !this.siteId) {
throw new Error('Cannot reconnect client: no controller reference');
}
await this.controller.request(
'POST',
`/api/s/${this.siteId}/cmd/stamgr`,
{
cmd: 'kick-sta',
mac: this.mac,
}
);
}
/**
* Rename this client
*/
public async rename(newName: string): Promise<void> {
if (!this.controller || !this.siteId) {
throw new Error('Cannot rename client: no controller reference');
}
// Check if user exists (has fixed IP/config)
if (this.user_id) {
await this.controller.request(
'PUT',
`/api/s/${this.siteId}/rest/user/${this.user_id}`,
{
name: newName,
}
);
} else {
// Create user entry for this client
await this.controller.request(
'POST',
`/api/s/${this.siteId}/rest/user`,
{
mac: this.mac,
name: newName,
}
);
}
this.name = newName;
}
/**
* Get data usage (combined TX and RX bytes)
*/
public getDataUsage(): number {
return (this.tx_bytes || 0) + (this.rx_bytes || 0);
}
/**
* Format data usage as human-readable string
*/
public getDataUsageFormatted(): string {
const bytes = this.getDataUsage();
if (bytes >= 1024 * 1024 * 1024) {
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
}
if (bytes >= 1024 * 1024) {
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
}
if (bytes >= 1024) {
return `${(bytes / 1024).toFixed(2)} KB`;
}
return `${bytes} B`;
}
}