@coffeerunhobby/mcp-ha-connect 2.2.1 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +1 -1
- package/README.md +11 -2
- package/dist/omadaClient/client.d.ts +6 -1
- package/dist/omadaClient/client.js +12 -1
- package/dist/omadaClient/client.js.map +1 -1
- package/dist/omadaClient/diagnose.d.ts +68 -0
- package/dist/omadaClient/diagnose.js +164 -0
- package/dist/omadaClient/diagnose.js.map +1 -0
- package/dist/omadaClient/index.d.ts +6 -0
- package/dist/omadaClient/index.js +15 -0
- package/dist/omadaClient/index.js.map +1 -1
- package/dist/omadaClient/macGroups.d.ts +58 -0
- package/dist/omadaClient/macGroups.js +124 -0
- package/dist/omadaClient/macGroups.js.map +1 -0
- package/dist/tools/infra/actions.d.ts +1 -1
- package/dist/tools/infra/actions.js +1 -1
- package/dist/tools/omada/index.js +6 -0
- package/dist/tools/omada/index.js.map +1 -1
- package/dist/tools/omada/macGroups.d.ts +19 -0
- package/dist/tools/omada/macGroups.js +77 -0
- package/dist/tools/omada/macGroups.js.map +1 -0
- package/dist/tools/omada/namespace.js +8 -0
- package/dist/tools/omada/namespace.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +5 -1
- package/.codex/review-rules.md +0 -16
- package/.dockerignore +0 -8
- package/.gitattributes +0 -30
- package/.github/workflows/ci.yml +0 -193
- package/AGENTS.md +0 -389
- package/Dockerfile +0 -50
- package/docker-compose.yml +0 -23
- package/docs/CHANGELOG.md +0 -341
- package/docs/COVERAGE.md +0 -219
- package/docs/QUICK_START.md +0 -240
- package/eslint.config.js +0 -33
- package/n8n/ai-agent-safety-monitor.json +0 -355
- package/openapi.json +0 -1259
- package/scripts/check-version-sync.mjs +0 -69
- package/scripts/generate-jwt.ts +0 -121
- package/src/config.ts +0 -440
- package/src/haClient/automations.ts +0 -261
- package/src/haClient/calendars.ts +0 -94
- package/src/haClient/config.ts +0 -52
- package/src/haClient/devices.ts +0 -653
- package/src/haClient/entities.ts +0 -128
- package/src/haClient/events.ts +0 -689
- package/src/haClient/history.ts +0 -73
- package/src/haClient/index.ts +0 -336
- package/src/haClient/request.ts +0 -142
- package/src/haClient/services.ts +0 -45
- package/src/haClient/states.ts +0 -60
- package/src/haClient/updates.ts +0 -58
- package/src/haClient/wsCommand.ts +0 -44
- package/src/index.ts +0 -164
- package/src/localAI/index.ts +0 -88
- package/src/localAI/prompts.ts +0 -50
- package/src/localAI/providers/base.ts +0 -120
- package/src/localAI/providers/ollama.ts +0 -78
- package/src/localAI/providers/openai.ts +0 -86
- package/src/localAI/types.ts +0 -67
- package/src/omadaClient/auth.ts +0 -178
- package/src/omadaClient/client.ts +0 -337
- package/src/omadaClient/device.ts +0 -118
- package/src/omadaClient/index.ts +0 -310
- package/src/omadaClient/network.ts +0 -284
- package/src/omadaClient/request.ts +0 -365
- package/src/omadaClient/security.ts +0 -51
- package/src/omadaClient/site.ts +0 -344
- package/src/ownerQuestions/service.ts +0 -536
- package/src/permissions/index.ts +0 -143
- package/src/resources/index.ts +0 -213
- package/src/server/auth.ts +0 -175
- package/src/server/chatFace.ts +0 -789
- package/src/server/chatSlice.ts +0 -57
- package/src/server/common.ts +0 -170
- package/src/server/eventSubscription.ts +0 -343
- package/src/server/http.ts +0 -626
- package/src/server/instructions.ts +0 -97
- package/src/server/rateLimiter.ts +0 -227
- package/src/server/stdio.ts +0 -41
- package/src/server/stream.ts +0 -205
- package/src/tools/ai/analyzeSensors.ts +0 -33
- package/src/tools/ai/index.ts +0 -20
- package/src/tools/common.ts +0 -442
- package/src/tools/homeassistant/askOwner.ts +0 -133
- package/src/tools/homeassistant/automationControls.ts +0 -66
- package/src/tools/homeassistant/calendar.ts +0 -97
- package/src/tools/homeassistant/callService.ts +0 -25
- package/src/tools/homeassistant/controlClimate.ts +0 -68
- package/src/tools/homeassistant/controlCover.ts +0 -48
- package/src/tools/homeassistant/controlFan.ts +0 -44
- package/src/tools/homeassistant/controlLight.ts +0 -38
- package/src/tools/homeassistant/controlMediaPlayer.ts +0 -50
- package/src/tools/homeassistant/createAutomation.ts +0 -35
- package/src/tools/homeassistant/deleteAutomation.ts +0 -24
- package/src/tools/homeassistant/entityAction.ts +0 -35
- package/src/tools/homeassistant/entityGuard.ts +0 -28
- package/src/tools/homeassistant/getAllSensors.ts +0 -46
- package/src/tools/homeassistant/getAutomationTrace.ts +0 -30
- package/src/tools/homeassistant/getDomainSummary.ts +0 -29
- package/src/tools/homeassistant/getEntitiesByDomain.ts +0 -56
- package/src/tools/homeassistant/getHistory.ts +0 -24
- package/src/tools/homeassistant/getState.ts +0 -24
- package/src/tools/homeassistant/getStates.ts +0 -46
- package/src/tools/homeassistant/getVersion.ts +0 -21
- package/src/tools/homeassistant/index.ts +0 -121
- package/src/tools/homeassistant/listAutomations.ts +0 -35
- package/src/tools/homeassistant/listEntities.ts +0 -44
- package/src/tools/homeassistant/listPersons.ts +0 -46
- package/src/tools/homeassistant/sceneAndScript.ts +0 -53
- package/src/tools/homeassistant/searchEntities.ts +0 -58
- package/src/tools/homeassistant/sendNotification.ts +0 -179
- package/src/tools/homeassistant/systemTools.ts +0 -52
- package/src/tools/homeassistant/triggerAutomation.ts +0 -24
- package/src/tools/index.ts +0 -78
- package/src/tools/infra/actions.ts +0 -95
- package/src/tools/infra/index.ts +0 -138
- package/src/tools/omada/blockClient.ts +0 -28
- package/src/tools/omada/cyclePoePort.ts +0 -32
- package/src/tools/omada/deleteClient.ts +0 -27
- package/src/tools/omada/disableClientRateLimit.ts +0 -23
- package/src/tools/omada/getClient.ts +0 -14
- package/src/tools/omada/getDevice.ts +0 -14
- package/src/tools/omada/getFirewallSetting.ts +0 -14
- package/src/tools/omada/getInternetInfo.ts +0 -14
- package/src/tools/omada/getLanNetworkList.ts +0 -14
- package/src/tools/omada/getLanProfileList.ts +0 -14
- package/src/tools/omada/getPortForwardingStatus.ts +0 -26
- package/src/tools/omada/getRateLimitProfiles.ts +0 -14
- package/src/tools/omada/getSiteNtpStatus.ts +0 -14
- package/src/tools/omada/getSsidDetail.ts +0 -22
- package/src/tools/omada/getSsidList.ts +0 -21
- package/src/tools/omada/getSwitchStackDetail.ts +0 -14
- package/src/tools/omada/getThreatList.ts +0 -42
- package/src/tools/omada/getWlanGroupList.ts +0 -14
- package/src/tools/omada/graph.ts +0 -187
- package/src/tools/omada/index.ts +0 -153
- package/src/tools/omada/listBlockedClients.ts +0 -17
- package/src/tools/omada/listClients.ts +0 -14
- package/src/tools/omada/listClientsActivity.ts +0 -25
- package/src/tools/omada/listClientsPastConnections.ts +0 -44
- package/src/tools/omada/listDevices.ts +0 -14
- package/src/tools/omada/listDevicesStats.ts +0 -41
- package/src/tools/omada/listMostActiveClients.ts +0 -14
- package/src/tools/omada/listSites.ts +0 -18
- package/src/tools/omada/namespace.ts +0 -1164
- package/src/tools/omada/searchDevices.ts +0 -20
- package/src/tools/omada/setClientRateLimit.ts +0 -26
- package/src/tools/omada/setClientRateLimitProfile.ts +0 -25
- package/src/tools/omada/setSiteNtpServers.ts +0 -36
- package/src/tools/omada/setSsidEnabled.ts +0 -38
- package/src/tools/omada/unblockClient.ts +0 -26
- package/src/tools/types.ts +0 -11
- package/src/tools/utils/pagination-schema.ts +0 -27
- package/src/types/ai/index.ts +0 -11
- package/src/types/ai/types.ts +0 -67
- package/src/types/homeassistant/automations.ts +0 -43
- package/src/types/homeassistant/calendars.ts +0 -26
- package/src/types/homeassistant/config.ts +0 -14
- package/src/types/homeassistant/entities.ts +0 -14
- package/src/types/homeassistant/errors.ts +0 -33
- package/src/types/homeassistant/haConfig.ts +0 -7
- package/src/types/homeassistant/history.ts +0 -13
- package/src/types/homeassistant/index.ts +0 -46
- package/src/types/homeassistant/request.ts +0 -6
- package/src/types/homeassistant/serviceCall.ts +0 -18
- package/src/types/homeassistant/states.ts +0 -58
- package/src/types/homeassistant/updates.ts +0 -28
- package/src/types/index.ts +0 -12
- package/src/types/omada/activeClientInfo.ts +0 -17
- package/src/types/omada/clientActivity.ts +0 -32
- package/src/types/omada/clientBlockStatus.ts +0 -11
- package/src/types/omada/clientPastConnection.ts +0 -82
- package/src/types/omada/index.ts +0 -14
- package/src/types/omada/omadaApiResponse.ts +0 -5
- package/src/types/omada/omadaClientInfo.ts +0 -11
- package/src/types/omada/omadaDeviceInfo.ts +0 -11
- package/src/types/omada/omadaDeviceStats.ts +0 -31
- package/src/types/omada/omadaSiteSummary.ts +0 -5
- package/src/types/omada/oswStackDetail.ts +0 -11
- package/src/types/omada/paginatedResult.ts +0 -6
- package/src/types/omada/rateLimitProfile.ts +0 -58
- package/src/types/omada/threatInfo.ts +0 -38
- package/src/types/omada/tokenResult.ts +0 -6
- package/src/types.ts +0 -2
- package/src/utils/config-validations.ts +0 -68
- package/src/utils/jwt.ts +0 -112
- package/src/utils/logger.ts +0 -121
- package/src/utils/plainText.ts +0 -48
- package/src/utils/redact.ts +0 -59
- package/src/utils/sanitizeError.ts +0 -18
- package/src/utils/tlsDispatcher.ts +0 -69
- package/src/version.ts +0 -5
- package/tests/bdd/automation-creation.spec.ts +0 -214
- package/tests/bdd/automation-management.spec.ts +0 -267
- package/tests/bdd/calendar.spec.ts +0 -190
- package/tests/bdd/configuration.spec.ts +0 -283
- package/tests/bdd/device-control.spec.ts +0 -344
- package/tests/bdd/entity-listing.spec.ts +0 -208
- package/tests/bdd/entity-search.spec.ts +0 -183
- package/tests/bdd/history-retrieval.spec.ts +0 -195
- package/tests/bdd/notifications.spec.ts +0 -238
- package/tests/bdd/service-calls.spec.ts +0 -144
- package/tests/bdd/state-retrieval.spec.ts +0 -204
- package/tests/bench/hotpaths.bench.ts +0 -68
- package/tests/config.test.ts +0 -355
- package/tests/features/automation-creation.feature +0 -36
- package/tests/features/automation-management.feature +0 -54
- package/tests/features/calendar.feature +0 -37
- package/tests/features/configuration.feature +0 -70
- package/tests/features/device-control.feature +0 -100
- package/tests/features/entity-listing.feature +0 -51
- package/tests/features/entity-search.feature +0 -44
- package/tests/features/history-retrieval.feature +0 -38
- package/tests/features/notifications.feature +0 -37
- package/tests/features/service-calls.feature +0 -35
- package/tests/features/state-retrieval.feature +0 -37
- package/tests/fixtures/openapi-default-slice.json +0 -290
- package/tests/haClient/automationTrace.test.ts +0 -59
- package/tests/haClient/automations.test.ts +0 -149
- package/tests/haClient/config.test.ts +0 -117
- package/tests/haClient/entities.test.ts +0 -188
- package/tests/haClient/events.test.ts +0 -455
- package/tests/haClient/eventsTls.test.ts +0 -52
- package/tests/haClient/history.test.ts +0 -228
- package/tests/haClient/index.test.ts +0 -243
- package/tests/haClient/request.test.ts +0 -217
- package/tests/haClient/services.test.ts +0 -120
- package/tests/haClient/states.test.ts +0 -119
- package/tests/haClient/updates.test.ts +0 -193
- package/tests/index.test.ts +0 -160
- package/tests/integration/homeassistant.test.ts +0 -144
- package/tests/integration/omada.test.ts +0 -167
- package/tests/integration/server.test.ts +0 -151
- package/tests/localAI/index.test.ts +0 -177
- package/tests/localAI/ollama.test.ts +0 -187
- package/tests/localAI/openai.test.ts +0 -257
- package/tests/omadaClient/actions.test.ts +0 -161
- package/tests/omadaClient/clientBlocks.test.ts +0 -170
- package/tests/omadaClient/ntp.test.ts +0 -381
- package/tests/omadaClient/site.test.ts +0 -37
- package/tests/ownerQuestions/service.test.ts +0 -499
- package/tests/ownerQuestions/tools.test.ts +0 -186
- package/tests/permissions/index.test.ts +0 -217
- package/tests/resources/index.test.ts +0 -288
- package/tests/scripts/generateJwt.test.ts +0 -44
- package/tests/security/accessControl.test.ts +0 -223
- package/tests/security/authentication.test.ts +0 -117
- package/tests/security/configuration.test.ts +0 -170
- package/tests/security/cryptoTransport.test.ts +0 -172
- package/tests/security/dependencies.test.ts +0 -152
- package/tests/security/injection.test.ts +0 -117
- package/tests/security/insecureDesign.test.ts +0 -121
- package/tests/security/logging.test.ts +0 -131
- package/tests/security/resourceConsumption.test.ts +0 -142
- package/tests/server/auth.test.ts +0 -249
- package/tests/server/chatFace.test.ts +0 -320
- package/tests/server/common.test.ts +0 -84
- package/tests/server/http.test.ts +0 -561
- package/tests/server/instructions.test.ts +0 -152
- package/tests/server/memoizeJsonSchema.test.ts +0 -71
- package/tests/server/stdio.test.ts +0 -99
- package/tests/server/stream.test.ts +0 -285
- package/tests/server/toolVisibility.http.test.ts +0 -111
- package/tests/server/toolVisibility.test.ts +0 -129
- package/tests/tools/ai/analyzeSensors.test.ts +0 -227
- package/tests/tools/common.test.ts +0 -123
- package/tests/tools/homeassistant/handlers.test.ts +0 -872
- package/tests/tools/infra/invokeAction.test.ts +0 -280
- package/tests/tools/omada/graph.test.ts +0 -385
- package/tests/tools/omada/handlers.test.ts +0 -897
- package/tests/tools/omada/namespace.test.ts +0 -483
- package/tests/tools/registry.test.ts +0 -168
- package/tests/utils/config-validations.test.ts +0 -352
- package/tests/utils/jwt.test.ts +0 -115
- package/tests/utils/logger.test.ts +0 -327
- package/tests/utils/redact.test.ts +0 -105
- package/tsconfig.json +0 -24
- package/vitest.config.ts +0 -38
package/src/localAI/types.ts
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Local AI Client Types
|
|
3
|
-
* Shared interfaces for AI provider implementations
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Supported AI provider types
|
|
8
|
-
* Use 'none' to explicitly disable AI features
|
|
9
|
-
*/
|
|
10
|
-
export type AIProviderType = 'ollama' | 'openai' | 'none';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Sensor data input for analysis
|
|
14
|
-
*/
|
|
15
|
-
export interface SensorData {
|
|
16
|
-
[entity_id: string]: {
|
|
17
|
-
state: string;
|
|
18
|
-
unit?: string;
|
|
19
|
-
attributes?: Record<string, unknown>;
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* AI analysis result
|
|
25
|
-
*/
|
|
26
|
-
export interface AnalysisResult {
|
|
27
|
-
severity: 'critical' | 'high' | 'medium' | 'low';
|
|
28
|
-
issue: string;
|
|
29
|
-
reasoning: string;
|
|
30
|
-
recommended_actions: string[];
|
|
31
|
-
ssh_diagnostics: string[];
|
|
32
|
-
confidence: number;
|
|
33
|
-
model: string;
|
|
34
|
-
provider: string;
|
|
35
|
-
response_time: number;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* AI provider configuration
|
|
40
|
-
*/
|
|
41
|
-
export interface AIProviderConfig {
|
|
42
|
-
provider: AIProviderType;
|
|
43
|
-
baseUrl: string;
|
|
44
|
-
model: string;
|
|
45
|
-
timeout: number;
|
|
46
|
-
apiKey?: string;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* AI provider interface - all providers must implement this
|
|
51
|
-
*/
|
|
52
|
-
export interface AIProviderInterface {
|
|
53
|
-
/**
|
|
54
|
-
* Analyze sensor data using AI
|
|
55
|
-
*/
|
|
56
|
-
analyzeSensors(sensors: SensorData): Promise<AnalysisResult>;
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Check if the AI provider is available
|
|
60
|
-
*/
|
|
61
|
-
checkHealth(): Promise<boolean>;
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Get the provider name
|
|
65
|
-
*/
|
|
66
|
-
getProviderName(): string;
|
|
67
|
-
}
|
package/src/omadaClient/auth.ts
DELETED
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
import type { Dispatcher } from 'undici';
|
|
2
|
-
import type { OmadaApiResponse, TokenResult } from '../types/index.js';
|
|
3
|
-
import { logger } from '../utils/logger.js';
|
|
4
|
-
import { createTlsDispatcher, tlsAwareFetch, type FetchInitWithDispatcher } from '../utils/tlsDispatcher.js';
|
|
5
|
-
|
|
6
|
-
const TOKEN_EXPIRY_BUFFER_SECONDS = 30;
|
|
7
|
-
|
|
8
|
-
export interface AuthManagerConfig {
|
|
9
|
-
baseUrl: string;
|
|
10
|
-
clientId: string;
|
|
11
|
-
clientSecret: string;
|
|
12
|
-
omadacId: string;
|
|
13
|
-
timeout?: number;
|
|
14
|
-
strictSsl?: boolean;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Authentication state management for the Omada client.
|
|
19
|
-
*/
|
|
20
|
-
export class AuthManager {
|
|
21
|
-
private accessToken?: string;
|
|
22
|
-
|
|
23
|
-
private refreshToken?: string;
|
|
24
|
-
|
|
25
|
-
private tokenExpiresAt?: number;
|
|
26
|
-
|
|
27
|
-
private readonly baseUrl: string;
|
|
28
|
-
private readonly clientId: string;
|
|
29
|
-
private readonly clientSecret: string;
|
|
30
|
-
private readonly omadacId: string;
|
|
31
|
-
private readonly timeout: number;
|
|
32
|
-
/** Per-client undici dispatcher; set only when strictSsl is disabled (H2). */
|
|
33
|
-
private readonly dispatcher?: Dispatcher;
|
|
34
|
-
|
|
35
|
-
constructor(config: AuthManagerConfig) {
|
|
36
|
-
this.baseUrl = config.baseUrl;
|
|
37
|
-
this.clientId = config.clientId;
|
|
38
|
-
this.clientSecret = config.clientSecret;
|
|
39
|
-
this.omadacId = config.omadacId;
|
|
40
|
-
this.timeout = config.timeout ?? 30000;
|
|
41
|
-
this.dispatcher = createTlsDispatcher(config.strictSsl, config.baseUrl);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Get the current access token, refreshing if necessary.
|
|
46
|
-
*/
|
|
47
|
-
public async getAccessToken(): Promise<string> {
|
|
48
|
-
await this.ensureAccessToken();
|
|
49
|
-
return this.accessToken ?? '';
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Clear the current authentication token.
|
|
54
|
-
*/
|
|
55
|
-
public clearToken(): void {
|
|
56
|
-
this.accessToken = undefined;
|
|
57
|
-
this.refreshToken = undefined;
|
|
58
|
-
this.tokenExpiresAt = undefined;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Ensure a valid access token is available, refreshing or re-authenticating if necessary.
|
|
63
|
-
*/
|
|
64
|
-
private async ensureAccessToken(): Promise<void> {
|
|
65
|
-
if (this.accessToken && this.tokenExpiresAt && Date.now() < this.tokenExpiresAt) {
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (this.refreshToken) {
|
|
70
|
-
try {
|
|
71
|
-
await this.authenticate('refresh_token');
|
|
72
|
-
return;
|
|
73
|
-
} catch {
|
|
74
|
-
this.clearToken();
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
await this.authenticate('client_credentials');
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Authenticate with the Omada controller using the specified grant type.
|
|
83
|
-
*/
|
|
84
|
-
private async authenticate(grantType: 'client_credentials' | 'refresh_token'): Promise<void> {
|
|
85
|
-
const url = new URL('/openapi/authorize/token', this.baseUrl);
|
|
86
|
-
|
|
87
|
-
// Add query parameters
|
|
88
|
-
url.searchParams.set('grant_type', grantType);
|
|
89
|
-
if (grantType === 'refresh_token') {
|
|
90
|
-
if (!this.refreshToken) {
|
|
91
|
-
throw new Error('No refresh token available to refresh the access token');
|
|
92
|
-
}
|
|
93
|
-
url.searchParams.set('refresh_token', this.refreshToken);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const body: Record<string, string> = {
|
|
97
|
-
client_id: this.clientId,
|
|
98
|
-
client_secret: this.clientSecret,
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
if (grantType === 'client_credentials') {
|
|
102
|
-
body.omadacId = this.omadacId;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
try {
|
|
106
|
-
// For self-signed certificates we relax TLS validation for THIS client
|
|
107
|
-
// only, via a per-instance undici dispatcher (H2) — never by toggling the
|
|
108
|
-
// process-global NODE_TLS_REJECT_UNAUTHORIZED env var.
|
|
109
|
-
const init: FetchInitWithDispatcher = {
|
|
110
|
-
method: 'POST',
|
|
111
|
-
headers: {
|
|
112
|
-
'Content-Type': 'application/json',
|
|
113
|
-
Accept: 'application/json',
|
|
114
|
-
},
|
|
115
|
-
body: JSON.stringify(body),
|
|
116
|
-
signal: AbortSignal.timeout(this.timeout),
|
|
117
|
-
};
|
|
118
|
-
if (this.dispatcher) {
|
|
119
|
-
init.dispatcher = this.dispatcher;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// tlsAwareFetch: dispatcher requests use undici's own fetch (pairing rule).
|
|
123
|
-
const response = await tlsAwareFetch(url.toString(), init);
|
|
124
|
-
|
|
125
|
-
if (!response.ok) {
|
|
126
|
-
const errorText = await response.text();
|
|
127
|
-
logger.error('Omada authentication HTTP error', {
|
|
128
|
-
grantType,
|
|
129
|
-
status: response.status,
|
|
130
|
-
statusText: response.statusText,
|
|
131
|
-
body: errorText,
|
|
132
|
-
});
|
|
133
|
-
throw new Error(`Omada authentication failed: ${response.statusText}`);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const data = (await response.json()) as OmadaApiResponse<TokenResult>;
|
|
137
|
-
|
|
138
|
-
if (data.errorCode !== 0) {
|
|
139
|
-
// Error code -44106 is returned by Omada for both invalid client credentials
|
|
140
|
-
// AND an incorrect omadacId — the API does not distinguish between them.
|
|
141
|
-
const hint = data.errorCode === -44106
|
|
142
|
-
? ' Check OMADA_CLIENT_ID, OMADA_CLIENT_SECRET, and OMADA_OMADAC_ID.'
|
|
143
|
-
: '';
|
|
144
|
-
const message = `${data.msg ?? 'Omada authentication failed'} (errorCode=${data.errorCode})${hint}`;
|
|
145
|
-
logger.error('Omada authentication error', {
|
|
146
|
-
errorCode: data.errorCode,
|
|
147
|
-
message: data.msg,
|
|
148
|
-
...(data.errorCode === -44106 && {
|
|
149
|
-
hint: 'Verify OMADA_CLIENT_ID, OMADA_CLIENT_SECRET, and OMADA_OMADAC_ID are all correct',
|
|
150
|
-
}),
|
|
151
|
-
});
|
|
152
|
-
throw new Error(message);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const token = data.result ?? ({} as TokenResult);
|
|
156
|
-
this.setToken(token);
|
|
157
|
-
} catch (error) {
|
|
158
|
-
logger.error('Omada authentication failed', {
|
|
159
|
-
grantType,
|
|
160
|
-
baseUrl: this.baseUrl,
|
|
161
|
-
error: error instanceof Error ? error.message : String(error),
|
|
162
|
-
});
|
|
163
|
-
throw error;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Store the authentication token and calculate expiration time.
|
|
169
|
-
*/
|
|
170
|
-
private setToken(token: TokenResult): void {
|
|
171
|
-
this.accessToken = token.accessToken;
|
|
172
|
-
this.refreshToken = token.refreshToken;
|
|
173
|
-
|
|
174
|
-
const expiresInSeconds = Number.isFinite(token.expiresIn) ? token.expiresIn : 0;
|
|
175
|
-
const expiresInMs = Math.max(expiresInSeconds - TOKEN_EXPIRY_BUFFER_SECONDS, 0) * 1000;
|
|
176
|
-
this.tokenExpiresAt = Date.now() + expiresInMs;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
@@ -1,337 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
ActiveClientInfo,
|
|
3
|
-
ClientActivity,
|
|
4
|
-
ClientBlockStatus,
|
|
5
|
-
ClientPastConnection,
|
|
6
|
-
ClientRateLimitSetting,
|
|
7
|
-
GetClientActivityOptions,
|
|
8
|
-
ListClientsPastConnectionsOptions,
|
|
9
|
-
OmadaApiResponse,
|
|
10
|
-
OmadaClientInfo,
|
|
11
|
-
PaginatedResult,
|
|
12
|
-
RateLimitProfile,
|
|
13
|
-
UpdateClientRateLimitRequest,
|
|
14
|
-
} from '../types/index.js';
|
|
15
|
-
|
|
16
|
-
import { OmadaApiError, type RequestHandler } from './request.js';
|
|
17
|
-
import type { SiteOperations } from './site.js';
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Client-related operations for the Omada API.
|
|
21
|
-
*/
|
|
22
|
-
export class ClientOperations {
|
|
23
|
-
constructor(
|
|
24
|
-
private readonly request: RequestHandler,
|
|
25
|
-
private readonly site: SiteOperations,
|
|
26
|
-
private readonly buildPath: (path: string) => string
|
|
27
|
-
) {}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* List all clients in a site.
|
|
31
|
-
*/
|
|
32
|
-
public async listClients(siteId?: string): Promise<OmadaClientInfo[]> {
|
|
33
|
-
const resolvedSiteId = this.site.resolveSiteId(siteId);
|
|
34
|
-
return await this.request.fetchPaginated<OmadaClientInfo>(this.buildPath(`/sites/${encodeURIComponent(resolvedSiteId)}/clients`));
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Get a specific client by MAC address or client ID.
|
|
39
|
-
*/
|
|
40
|
-
public async getClient(identifier: string, siteId?: string): Promise<OmadaClientInfo | undefined> {
|
|
41
|
-
const clients = await this.listClients(siteId);
|
|
42
|
-
return clients.find((client) => client.mac === identifier || client.id === identifier);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Get most active clients in a site (dashboard endpoint).
|
|
47
|
-
* Returns clients sorted by total traffic.
|
|
48
|
-
*
|
|
49
|
-
* @param siteId - Optional site ID, uses default from config if not provided
|
|
50
|
-
* @returns Array of active client information
|
|
51
|
-
*/
|
|
52
|
-
public async listMostActiveClients(siteId?: string): Promise<ActiveClientInfo[]> {
|
|
53
|
-
const resolvedSiteId = this.site.resolveSiteId(siteId);
|
|
54
|
-
const response = await this.request.get<OmadaApiResponse<ActiveClientInfo[]>>(
|
|
55
|
-
this.buildPath(`/sites/${encodeURIComponent(resolvedSiteId)}/dashboard/active-clients`)
|
|
56
|
-
);
|
|
57
|
-
return response.result ?? [];
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Get client activity statistics over time (dashboard endpoint).
|
|
62
|
-
* Returns time-series data about new, active, and disconnected clients.
|
|
63
|
-
*
|
|
64
|
-
* @param options - Options including optional siteId, start, and end timestamps
|
|
65
|
-
* @returns Array of client activity snapshots over time
|
|
66
|
-
*/
|
|
67
|
-
public async listClientsActivity(options: GetClientActivityOptions = {}): Promise<ClientActivity[]> {
|
|
68
|
-
const resolvedSiteId = this.site.resolveSiteId(options.siteId);
|
|
69
|
-
const params: Record<string, unknown> = {};
|
|
70
|
-
|
|
71
|
-
if (options.start !== undefined) {
|
|
72
|
-
params.start = options.start;
|
|
73
|
-
}
|
|
74
|
-
if (options.end !== undefined) {
|
|
75
|
-
params.end = options.end;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const response = await this.request.get<OmadaApiResponse<ClientActivity[]>>(
|
|
79
|
-
this.buildPath(`/sites/${encodeURIComponent(resolvedSiteId)}/dashboard/client-activity`),
|
|
80
|
-
params
|
|
81
|
-
);
|
|
82
|
-
return response.result ?? [];
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Get client past connection list (insight endpoint).
|
|
87
|
-
* Returns historical client connection data with support for pagination, filtering, and sorting.
|
|
88
|
-
*
|
|
89
|
-
* @param options - Options including siteId, pagination, filters, and search parameters
|
|
90
|
-
* @returns Array of client past connection information
|
|
91
|
-
*/
|
|
92
|
-
public async listClientsPastConnections(options: ListClientsPastConnectionsOptions): Promise<ClientPastConnection[]> {
|
|
93
|
-
const resolvedSiteId = this.site.resolveSiteId(options.siteId);
|
|
94
|
-
const params: Record<string, unknown> = {
|
|
95
|
-
page: options.page,
|
|
96
|
-
pageSize: options.pageSize,
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
// Add optional sort parameter
|
|
100
|
-
if (options.sortLastSeen !== undefined) {
|
|
101
|
-
params['sorts.lastSeen'] = options.sortLastSeen;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Add optional filter parameters
|
|
105
|
-
if (options.timeStart !== undefined) {
|
|
106
|
-
params['filters.timeStart'] = String(options.timeStart);
|
|
107
|
-
}
|
|
108
|
-
if (options.timeEnd !== undefined) {
|
|
109
|
-
params['filters.timeEnd'] = String(options.timeEnd);
|
|
110
|
-
}
|
|
111
|
-
if (options.guest !== undefined) {
|
|
112
|
-
params['filters.guest'] = String(options.guest);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Add optional search parameter
|
|
116
|
-
if (options.searchKey !== undefined) {
|
|
117
|
-
params.searchKey = options.searchKey;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const response = await this.request.get<OmadaApiResponse<PaginatedResult<ClientPastConnection>>>(
|
|
121
|
-
this.buildPath(`/sites/${encodeURIComponent(resolvedSiteId)}/insight/past-connection`),
|
|
122
|
-
params
|
|
123
|
-
);
|
|
124
|
-
|
|
125
|
-
const result = this.request.ensureSuccess(response);
|
|
126
|
-
return result.data ?? [];
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Get rate limit profile list for a site.
|
|
131
|
-
* Returns available rate limit profiles that can be applied to clients.
|
|
132
|
-
*
|
|
133
|
-
* @param siteId - Optional site ID, uses default from config if not provided
|
|
134
|
-
* @returns Array of rate limit profiles
|
|
135
|
-
*/
|
|
136
|
-
public async getRateLimitProfiles(siteId?: string): Promise<RateLimitProfile[]> {
|
|
137
|
-
const resolvedSiteId = this.site.resolveSiteId(siteId);
|
|
138
|
-
const response = await this.request.get<OmadaApiResponse<RateLimitProfile[]>>(
|
|
139
|
-
this.buildPath(`/sites/${encodeURIComponent(resolvedSiteId)}/rate-limit-profiles`)
|
|
140
|
-
);
|
|
141
|
-
return response.result ?? [];
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Set custom rate limit for a client.
|
|
146
|
-
* Configures download and upload bandwidth limits directly without using a profile.
|
|
147
|
-
*
|
|
148
|
-
* @param clientMac - MAC address of the client
|
|
149
|
-
* @param downLimit - Download limit in Kbps
|
|
150
|
-
* @param upLimit - Upload limit in Kbps
|
|
151
|
-
* @param siteId - Optional site ID, uses default from config if not provided
|
|
152
|
-
* @returns Updated rate limit setting
|
|
153
|
-
*/
|
|
154
|
-
public async setClientRateLimit(clientMac: string, downLimit: number, upLimit: number, siteId?: string): Promise<ClientRateLimitSetting> {
|
|
155
|
-
const resolvedSiteId = this.site.resolveSiteId(siteId);
|
|
156
|
-
const requestBody: UpdateClientRateLimitRequest = {
|
|
157
|
-
mode: 0, // 0 = custom rate limit
|
|
158
|
-
customRateLimit: {
|
|
159
|
-
enable: true,
|
|
160
|
-
upEnable: true,
|
|
161
|
-
upLimit: upLimit,
|
|
162
|
-
downEnable: true,
|
|
163
|
-
downLimit: downLimit,
|
|
164
|
-
},
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
const response = await this.request.patch<OmadaApiResponse<ClientRateLimitSetting>>(
|
|
168
|
-
this.buildPath(`/sites/${encodeURIComponent(resolvedSiteId)}/clients/${encodeURIComponent(clientMac)}/ratelimit`),
|
|
169
|
-
requestBody
|
|
170
|
-
);
|
|
171
|
-
return this.request.ensureSuccess(response);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Set rate limit profile for a client.
|
|
176
|
-
* Applies a predefined rate limit profile to the client.
|
|
177
|
-
*
|
|
178
|
-
* @param clientMac - MAC address of the client
|
|
179
|
-
* @param profileId - Rate limit profile ID
|
|
180
|
-
* @param siteId - Optional site ID, uses default from config if not provided
|
|
181
|
-
* @returns Updated rate limit setting
|
|
182
|
-
*/
|
|
183
|
-
public async setClientRateLimitProfile(clientMac: string, profileId: string, siteId?: string): Promise<ClientRateLimitSetting> {
|
|
184
|
-
const resolvedSiteId = this.site.resolveSiteId(siteId);
|
|
185
|
-
const requestBody: UpdateClientRateLimitRequest = {
|
|
186
|
-
mode: 1, // 1 = use rate limit profile
|
|
187
|
-
rateLimitProfileId: profileId,
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
const response = await this.request.patch<OmadaApiResponse<ClientRateLimitSetting>>(
|
|
191
|
-
this.buildPath(`/sites/${encodeURIComponent(resolvedSiteId)}/clients/${encodeURIComponent(clientMac)}/ratelimit`),
|
|
192
|
-
requestBody
|
|
193
|
-
);
|
|
194
|
-
return this.request.ensureSuccess(response);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Disable rate limit for a client.
|
|
199
|
-
* Removes any rate limiting applied to the client.
|
|
200
|
-
*
|
|
201
|
-
* @param clientMac - MAC address of the client
|
|
202
|
-
* @param siteId - Optional site ID, uses default from config if not provided
|
|
203
|
-
* @returns Updated rate limit setting
|
|
204
|
-
*/
|
|
205
|
-
public async disableClientRateLimit(clientMac: string, siteId?: string): Promise<ClientRateLimitSetting> {
|
|
206
|
-
const resolvedSiteId = this.site.resolveSiteId(siteId);
|
|
207
|
-
|
|
208
|
-
// To disable rate limiting, use mode 0 with enable: false and minimal valid limit values
|
|
209
|
-
const requestBody: UpdateClientRateLimitRequest = {
|
|
210
|
-
mode: 0,
|
|
211
|
-
customRateLimit: {
|
|
212
|
-
enable: false,
|
|
213
|
-
upEnable: false,
|
|
214
|
-
upLimit: 1,
|
|
215
|
-
downEnable: false,
|
|
216
|
-
downLimit: 1,
|
|
217
|
-
},
|
|
218
|
-
};
|
|
219
|
-
|
|
220
|
-
const response = await this.request.patch<OmadaApiResponse<ClientRateLimitSetting>>(
|
|
221
|
-
this.buildPath(`/sites/${encodeURIComponent(resolvedSiteId)}/clients/${encodeURIComponent(clientMac)}/ratelimit`),
|
|
222
|
-
requestBody
|
|
223
|
-
);
|
|
224
|
-
return this.request.ensureSuccess(response);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Block a client from the network.
|
|
229
|
-
* The client is denied network access until it is unblocked.
|
|
230
|
-
*
|
|
231
|
-
* @param clientMac - MAC address of the client to block
|
|
232
|
-
* @param siteId - Optional site ID, uses default from config if not provided
|
|
233
|
-
* @returns Status object confirming the client was blocked
|
|
234
|
-
*/
|
|
235
|
-
public async blockClient(clientMac: string, siteId?: string): Promise<ClientBlockStatus> {
|
|
236
|
-
const resolvedSiteId = this.site.resolveSiteId(siteId);
|
|
237
|
-
const response = await this.request.post<OmadaApiResponse<unknown>>(
|
|
238
|
-
this.buildPath(`/sites/${encodeURIComponent(resolvedSiteId)}/clients/${encodeURIComponent(clientMac)}/block`)
|
|
239
|
-
);
|
|
240
|
-
this.request.ensureSuccess(response);
|
|
241
|
-
return { mac: clientMac, siteId: resolvedSiteId, blocked: true };
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* Unblock a previously blocked client, restoring its network access.
|
|
246
|
-
*
|
|
247
|
-
* @param clientMac - MAC address of the client to unblock
|
|
248
|
-
* @param siteId - Optional site ID, uses default from config if not provided
|
|
249
|
-
* @returns Status object confirming the client was unblocked
|
|
250
|
-
*/
|
|
251
|
-
public async unblockClient(clientMac: string, siteId?: string): Promise<ClientBlockStatus> {
|
|
252
|
-
const resolvedSiteId = this.site.resolveSiteId(siteId);
|
|
253
|
-
try {
|
|
254
|
-
const response = await this.request.post<OmadaApiResponse<unknown>>(
|
|
255
|
-
this.buildPath(`/sites/${encodeURIComponent(resolvedSiteId)}/clients/${encodeURIComponent(clientMac)}/unblock`)
|
|
256
|
-
);
|
|
257
|
-
this.request.ensureSuccess(response);
|
|
258
|
-
return { mac: clientMac, siteId: resolvedSiteId, blocked: false };
|
|
259
|
-
} catch (error) {
|
|
260
|
-
// The controller may report this with HTTP 200 or an HTTP error status.
|
|
261
|
-
if (!(error instanceof OmadaApiError) || error.errorCode !== CLIENT_DOES_NOT_EXIST) {
|
|
262
|
-
throw error;
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
// The unblock endpoint only knows clients the controller still treats as
|
|
266
|
-
// current; a blocked client cannot reconnect, so after a while it drops
|
|
267
|
-
// out and the API can no longer unblock it. Check the known-clients list.
|
|
268
|
-
const known = await this.findKnownClient(clientMac, resolvedSiteId);
|
|
269
|
-
// Only an explicit block=false proves the client is unblocked; a record
|
|
270
|
-
// without block state proves nothing.
|
|
271
|
-
if (known?.block === false) {
|
|
272
|
-
return { mac: clientMac, siteId: resolvedSiteId, blocked: false };
|
|
273
|
-
}
|
|
274
|
-
throw new Error(
|
|
275
|
-
known
|
|
276
|
-
? `Omada's API cannot unblock ${clientMac}: the client is offline and the controller no longer treats it as current. ` +
|
|
277
|
-
'Unblock it in the Omada web UI (Insights > Known Clients), or remove its record with omada_deleteClient.'
|
|
278
|
-
: `Omada has no record of ${clientMac}, but its access points may still refuse it (an orphaned block). ` +
|
|
279
|
-
'Try omada_deleteClient, or let the device reconnect by cable so the controller sees it again, then unblock it.'
|
|
280
|
-
);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
/**
|
|
284
|
-
* Clients the controller remembers (online or not), with their block state.
|
|
285
|
-
* OperationId: getKnownClients (GET /insight/clients)
|
|
286
|
-
*/
|
|
287
|
-
public async listKnownClients(siteId?: string): Promise<KnownClient[]> {
|
|
288
|
-
const resolvedSiteId = this.site.resolveSiteId(siteId);
|
|
289
|
-
return await this.request.fetchPaginated<KnownClient>(
|
|
290
|
-
this.buildPath(`/sites/${encodeURIComponent(resolvedSiteId)}/insight/clients`)
|
|
291
|
-
);
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
/** Known clients that are currently blocked. */
|
|
295
|
-
public async listBlockedClients(siteId?: string): Promise<KnownClient[]> {
|
|
296
|
-
return (await this.listKnownClients(siteId)).filter((client) => client.block === true);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* Delete the controller's record of a client: its name, history and block
|
|
301
|
-
* state. Clears an orphaned block that the unblock endpoint can no longer reach.
|
|
302
|
-
* OperationId: deleteClient (DELETE /clients/{clientMac})
|
|
303
|
-
*/
|
|
304
|
-
public async deleteClient(clientMac: string, siteId?: string): Promise<{ mac: string; siteId: string; deleted: true }> {
|
|
305
|
-
const resolvedSiteId = this.site.resolveSiteId(siteId);
|
|
306
|
-
const response = await this.request.request<OmadaApiResponse<unknown>>({
|
|
307
|
-
method: 'DELETE',
|
|
308
|
-
url: this.buildPath(`/sites/${encodeURIComponent(resolvedSiteId)}/clients/${encodeURIComponent(clientMac)}`),
|
|
309
|
-
});
|
|
310
|
-
this.request.ensureSuccess(response);
|
|
311
|
-
return { mac: clientMac, siteId: resolvedSiteId, deleted: true };
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
private async findKnownClient(clientMac: string, siteId: string): Promise<KnownClient | undefined> {
|
|
315
|
-
const wanted = normalizeMac(clientMac);
|
|
316
|
-
return (await this.listKnownClients(siteId)).find((client) => normalizeMac(client.mac) === wanted);
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
/** Omada error code: "This client does not exist." */
|
|
321
|
-
export const CLIENT_DOES_NOT_EXIST = -41004;
|
|
322
|
-
|
|
323
|
-
/** A known client (KnownClientVO), as GET /insight/clients returns it. */
|
|
324
|
-
export interface KnownClient {
|
|
325
|
-
mac: string;
|
|
326
|
-
name?: string;
|
|
327
|
-
wireless?: boolean;
|
|
328
|
-
guest?: boolean;
|
|
329
|
-
lastSeen?: number;
|
|
330
|
-
block?: boolean;
|
|
331
|
-
[field: string]: unknown;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
/** "4c:1d:96:8d:37:c7", "4C-1D-96-8D-37-C7" and "4c1d968d37c7" compare equal. */
|
|
335
|
-
export function normalizeMac(mac: string): string {
|
|
336
|
-
return mac.replace(/[^0-9a-f]/gi, '').toUpperCase();
|
|
337
|
-
}
|