Files
unifi/ts/classes.unifi-access.ts

340 lines
8.2 KiB
TypeScript
Raw Permalink Normal View History

import * as plugins from './plugins.js';
import { logger } from './unifi.logger.js';
import { UnifiHttp } from './classes.unifihttp.js';
import { DoorManager } from './classes.doormanager.js';
import type {
IUnifiAccessOptions,
IAccessDevice,
IAccessUser,
IAccessPolicy,
IAccessLocation,
IAccessEvent,
IAccessApiResponse,
THttpMethod,
} from './interfaces/index.js';
/**
* UniFi Access - Entry point for Access Controller API
*
* This class provides access to the UniFi Access API for managing doors,
* users, credentials, and access events. It uses bearer token authentication.
*
* @example
* ```typescript
* const access = new UnifiAccess({
* host: '192.168.1.1',
* token: 'your-bearer-token',
* });
*
* const doors = await access.doorManager.listDoors();
* const users = await access.getUsers();
*
* // Unlock a door
* await access.unlockDoor('door-id');
* ```
*/
export class UnifiAccess {
/** Access API port */
private static readonly API_PORT = 12445;
/** Access host */
private host: string;
/** Bearer token for authentication */
private token: string;
/** Whether to verify SSL certificates */
private verifySsl: boolean;
/** HTTP client */
private http: UnifiHttp;
/** Door manager instance */
public doorManager: DoorManager;
constructor(options: IUnifiAccessOptions) {
this.host = options.host.replace(/\/$/, '');
this.token = options.token;
this.verifySsl = options.verifySsl ?? false;
// Build base URL with Access API port
const baseHost = this.host.startsWith('http') ? this.host : `https://${this.host}`;
// Access API is typically on port 12445 at /api/v1/developer
const baseUrl = `${baseHost}:${UnifiAccess.API_PORT}/api/v1/developer`;
this.http = new UnifiHttp(baseUrl, this.verifySsl);
this.http.setHeader('Authorization', `Bearer ${this.token}`);
// Initialize managers
this.doorManager = new DoorManager(this);
logger.log('info', `UnifiAccess initialized for ${this.host}`);
}
/**
* Make a request to the Access API
*/
public async request<T>(
method: THttpMethod,
endpoint: string,
data?: unknown
): Promise<T> {
return this.http.request<T>(method, endpoint, data);
}
/**
* Unlock a door by ID
*/
public async unlockDoor(doorId: string): Promise<void> {
logger.log('info', `Unlocking door: ${doorId}`);
await this.request('PUT', `/door/${doorId}/unlock`);
}
/**
* Lock a door by ID
*/
public async lockDoor(doorId: string): Promise<void> {
logger.log('info', `Locking door: ${doorId}`);
await this.request('PUT', `/door/${doorId}/lock`);
}
/**
* Get all doors (convenience method)
*/
public async getDoors() {
return this.doorManager.listDoors();
}
/**
* Get all devices (hubs, readers, etc.)
*/
public async getDevices(): Promise<IAccessDevice[]> {
logger.log('debug', 'Fetching Access devices');
const response = await this.request<IAccessApiResponse<IAccessDevice[]>>(
'GET',
'/device'
);
return response.data || [];
}
/**
* Get a device by ID
*/
public async getDeviceById(deviceId: string): Promise<IAccessDevice | null> {
try {
const response = await this.request<IAccessApiResponse<IAccessDevice>>(
'GET',
`/device/${deviceId}`
);
return response.data;
} catch {
return null;
}
}
/**
* Get all users/credential holders
*/
public async getUsers(): Promise<IAccessUser[]> {
logger.log('debug', 'Fetching Access users');
const response = await this.request<IAccessApiResponse<IAccessUser[]>>(
'GET',
'/user'
);
return response.data || [];
}
/**
* Get a user by ID
*/
public async getUserById(userId: string): Promise<IAccessUser | null> {
try {
const response = await this.request<IAccessApiResponse<IAccessUser>>(
'GET',
`/user/${userId}`
);
return response.data;
} catch {
return null;
}
}
/**
* Create a new user
*/
public async createUser(user: Partial<IAccessUser>): Promise<IAccessUser> {
logger.log('info', `Creating user: ${user.first_name} ${user.last_name}`);
const response = await this.request<IAccessApiResponse<IAccessUser>>(
'POST',
'/user',
user
);
return response.data;
}
/**
* Update a user
*/
public async updateUser(userId: string, user: Partial<IAccessUser>): Promise<IAccessUser> {
logger.log('info', `Updating user: ${userId}`);
const response = await this.request<IAccessApiResponse<IAccessUser>>(
'PUT',
`/user/${userId}`,
user
);
return response.data;
}
/**
* Delete a user
*/
public async deleteUser(userId: string): Promise<void> {
logger.log('info', `Deleting user: ${userId}`);
await this.request('DELETE', `/user/${userId}`);
}
/**
* Get access policies/groups
*/
public async getPolicies(): Promise<IAccessPolicy[]> {
logger.log('debug', 'Fetching Access policies');
const response = await this.request<IAccessApiResponse<IAccessPolicy[]>>(
'GET',
'/policy'
);
return response.data || [];
}
/**
* Get a policy by ID
*/
public async getPolicyById(policyId: string): Promise<IAccessPolicy | null> {
try {
const response = await this.request<IAccessApiResponse<IAccessPolicy>>(
'GET',
`/policy/${policyId}`
);
return response.data;
} catch {
return null;
}
}
/**
* Get locations
*/
public async getLocations(): Promise<IAccessLocation[]> {
logger.log('debug', 'Fetching Access locations');
const response = await this.request<IAccessApiResponse<IAccessLocation[]>>(
'GET',
'/location'
);
return response.data || [];
}
/**
* Get access events (entry log)
*/
public async getEvents(
options: { start?: number; end?: number; limit?: number; doorId?: string; userId?: string } = {}
): Promise<IAccessEvent[]> {
logger.log('debug', 'Fetching Access events');
const params = new URLSearchParams();
if (options.start) params.append('start', options.start.toString());
if (options.end) params.append('end', options.end.toString());
if (options.limit) params.append('limit', options.limit.toString());
if (options.doorId) params.append('door_id', options.doorId);
if (options.userId) params.append('user_id', options.userId);
const queryString = params.toString() ? `?${params.toString()}` : '';
const response = await this.request<IAccessApiResponse<IAccessEvent[]>>(
'GET',
`/event${queryString}`
);
return response.data || [];
}
/**
* Get recent access events
*/
public async getRecentEvents(limit: number = 100): Promise<IAccessEvent[]> {
return this.getEvents({ limit });
}
/**
* Grant user access to a door
*/
public async grantAccess(userId: string, doorId: string): Promise<void> {
logger.log('info', `Granting user ${userId} access to door ${doorId}`);
await this.request('POST', `/user/${userId}/access`, {
door_id: doorId,
});
}
/**
* Revoke user access from a door
*/
public async revokeAccess(userId: string, doorId: string): Promise<void> {
logger.log('info', `Revoking user ${userId} access from door ${doorId}`);
await this.request('DELETE', `/user/${userId}/access/${doorId}`);
}
/**
* Assign NFC card to user
*/
public async assignNfcCard(
userId: string,
cardToken: string,
alias?: string
): Promise<void> {
logger.log('info', `Assigning NFC card to user ${userId}`);
await this.request('POST', `/user/${userId}/nfc_card`, {
token: cardToken,
alias: alias,
});
}
/**
* Remove NFC card from user
*/
public async removeNfcCard(userId: string, cardId: string): Promise<void> {
logger.log('info', `Removing NFC card ${cardId} from user ${userId}`);
await this.request('DELETE', `/user/${userId}/nfc_card/${cardId}`);
}
/**
* Set user PIN code
*/
public async setUserPin(userId: string, pinCode: string): Promise<void> {
logger.log('info', `Setting PIN for user ${userId}`);
await this.request('PUT', `/user/${userId}`, {
pin_code: pinCode,
});
}
}