Files
unifi/ts/classes.clientmanager.ts

263 lines
7.5 KiB
TypeScript

import type { UnifiController } from './classes.unifi-controller.js';
import { UnifiClient } from './classes.client.js';
import type { INetworkClient, IUnifiApiResponse } from './interfaces/index.js';
import { logger } from './unifi.logger.js';
/**
* Manager for UniFi network clients
*/
export class ClientManager {
private controller: UnifiController;
constructor(controller: UnifiController) {
this.controller = controller;
}
/**
* List all active (connected) clients for a site
*/
public async listActiveClients(siteId: string = 'default'): Promise<UnifiClient[]> {
logger.log('debug', `Fetching active clients for site: ${siteId}`);
const response = await this.controller.request<IUnifiApiResponse<INetworkClient>>(
'GET',
`/api/s/${siteId}/stat/sta`
);
const clients: UnifiClient[] = [];
for (const clientData of response.data || []) {
clients.push(UnifiClient.createFromApiObject(clientData, this.controller, siteId));
}
logger.log('info', `Found ${clients.length} active clients`);
return clients;
}
/**
* List all known clients (including historical)
*/
public async listAllClients(siteId: string = 'default'): Promise<UnifiClient[]> {
logger.log('debug', `Fetching all known clients for site: ${siteId}`);
const response = await this.controller.request<IUnifiApiResponse<INetworkClient>>(
'GET',
`/api/s/${siteId}/rest/user`
);
const clients: UnifiClient[] = [];
for (const clientData of response.data || []) {
clients.push(UnifiClient.createFromApiObject(clientData, this.controller, siteId));
}
logger.log('info', `Found ${clients.length} known clients`);
return clients;
}
/**
* Get a client by MAC address
*/
public async getClientByMac(mac: string, siteId: string = 'default'): Promise<UnifiClient | null> {
const normalizedMac = mac.toLowerCase().replace(/[:-]/g, ':');
const clients = await this.listActiveClients(siteId);
return clients.find((client) => client.mac.toLowerCase() === normalizedMac) || null;
}
/**
* Get a client by IP address
*/
public async getClientByIp(ip: string, siteId: string = 'default'): Promise<UnifiClient | null> {
const clients = await this.listActiveClients(siteId);
return clients.find((client) => client.ip === ip) || null;
}
/**
* Get a client by hostname
*/
public async getClientByHostname(hostname: string, siteId: string = 'default'): Promise<UnifiClient | null> {
const clients = await this.listActiveClients(siteId);
return clients.find(
(client) => client.hostname?.toLowerCase() === hostname.toLowerCase()
) || null;
}
/**
* Get wired clients only
*/
public async getWiredClients(siteId: string = 'default'): Promise<UnifiClient[]> {
const clients = await this.listActiveClients(siteId);
return clients.filter((client) => client.is_wired);
}
/**
* Get wireless clients only
*/
public async getWirelessClients(siteId: string = 'default'): Promise<UnifiClient[]> {
const clients = await this.listActiveClients(siteId);
return clients.filter((client) => client.isWireless());
}
/**
* Get guest clients only
*/
public async getGuestClients(siteId: string = 'default'): Promise<UnifiClient[]> {
const clients = await this.listActiveClients(siteId);
return clients.filter((client) => client.isGuest());
}
/**
* Get clients connected to a specific AP
*/
public async getClientsByAP(apMac: string, siteId: string = 'default'): Promise<UnifiClient[]> {
const normalizedMac = apMac.toLowerCase();
const clients = await this.listActiveClients(siteId);
return clients.filter((client) => client.uplink_mac?.toLowerCase() === normalizedMac);
}
/**
* Get clients on a specific SSID
*/
public async getClientsBySSID(ssid: string, siteId: string = 'default'): Promise<UnifiClient[]> {
const clients = await this.listActiveClients(siteId);
return clients.filter((client) => client.essid === ssid);
}
/**
* Block a client by MAC address
*/
public async blockClient(mac: string, siteId: string = 'default'): Promise<void> {
logger.log('info', `Blocking client: ${mac}`);
await this.controller.request(
'POST',
`/api/s/${siteId}/cmd/stamgr`,
{
cmd: 'block-sta',
mac: mac.toLowerCase(),
}
);
}
/**
* Unblock a client by MAC address
*/
public async unblockClient(mac: string, siteId: string = 'default'): Promise<void> {
logger.log('info', `Unblocking client: ${mac}`);
await this.controller.request(
'POST',
`/api/s/${siteId}/cmd/stamgr`,
{
cmd: 'unblock-sta',
mac: mac.toLowerCase(),
}
);
}
/**
* Reconnect (kick) a client by MAC address
*/
public async reconnectClient(mac: string, siteId: string = 'default'): Promise<void> {
logger.log('info', `Reconnecting client: ${mac}`);
await this.controller.request(
'POST',
`/api/s/${siteId}/cmd/stamgr`,
{
cmd: 'kick-sta',
mac: mac.toLowerCase(),
}
);
}
/**
* Authorize a guest client
*/
public async authorizeGuest(
mac: string,
minutes: number = 60,
options: {
up?: number; // Upload bandwidth limit in Kbps
down?: number; // Download bandwidth limit in Kbps
bytes?: number; // Data quota in MB
apMac?: string; // Specific AP MAC
} = {},
siteId: string = 'default'
): Promise<void> {
logger.log('info', `Authorizing guest: ${mac} for ${minutes} minutes`);
await this.controller.request(
'POST',
`/api/s/${siteId}/cmd/stamgr`,
{
cmd: 'authorize-guest',
mac: mac.toLowerCase(),
minutes,
up: options.up,
down: options.down,
bytes: options.bytes,
ap_mac: options.apMac?.toLowerCase(),
}
);
}
/**
* Unauthorize a guest client
*/
public async unauthorizeGuest(mac: string, siteId: string = 'default'): Promise<void> {
logger.log('info', `Unauthorizing guest: ${mac}`);
await this.controller.request(
'POST',
`/api/s/${siteId}/cmd/stamgr`,
{
cmd: 'unauthorize-guest',
mac: mac.toLowerCase(),
}
);
}
/**
* Set or update client name/alias
*/
public async setClientName(mac: string, name: string, siteId: string = 'default'): Promise<void> {
logger.log('info', `Setting client name: ${mac} -> ${name}`);
// First try to find existing user
const allClients = await this.listAllClients(siteId);
const existingClient = allClients.find(
(c) => c.mac.toLowerCase() === mac.toLowerCase()
);
if (existingClient?.user_id) {
// Update existing user
await this.controller.request(
'PUT',
`/api/s/${siteId}/rest/user/${existingClient.user_id}`,
{ name }
);
} else {
// Create new user entry
await this.controller.request(
'POST',
`/api/s/${siteId}/rest/user`,
{
mac: mac.toLowerCase(),
name,
}
);
}
}
/**
* Get blocked clients
*/
public async getBlockedClients(siteId: string = 'default'): Promise<UnifiClient[]> {
logger.log('debug', `Fetching blocked clients for site: ${siteId}`);
// Blocked clients are in the user endpoint with blocked=true
const allClients = await this.listAllClients(siteId);
// Note: This filtering may need adjustment based on actual API response
return allClients.filter((client) => (client as any).blocked === true);
}
}