@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.
Files changed (281) hide show
  1. package/.env.example +1 -1
  2. package/README.md +11 -2
  3. package/dist/omadaClient/client.d.ts +6 -1
  4. package/dist/omadaClient/client.js +12 -1
  5. package/dist/omadaClient/client.js.map +1 -1
  6. package/dist/omadaClient/diagnose.d.ts +68 -0
  7. package/dist/omadaClient/diagnose.js +164 -0
  8. package/dist/omadaClient/diagnose.js.map +1 -0
  9. package/dist/omadaClient/index.d.ts +6 -0
  10. package/dist/omadaClient/index.js +15 -0
  11. package/dist/omadaClient/index.js.map +1 -1
  12. package/dist/omadaClient/macGroups.d.ts +58 -0
  13. package/dist/omadaClient/macGroups.js +124 -0
  14. package/dist/omadaClient/macGroups.js.map +1 -0
  15. package/dist/tools/infra/actions.d.ts +1 -1
  16. package/dist/tools/infra/actions.js +1 -1
  17. package/dist/tools/omada/index.js +6 -0
  18. package/dist/tools/omada/index.js.map +1 -1
  19. package/dist/tools/omada/macGroups.d.ts +19 -0
  20. package/dist/tools/omada/macGroups.js +77 -0
  21. package/dist/tools/omada/macGroups.js.map +1 -0
  22. package/dist/tools/omada/namespace.js +8 -0
  23. package/dist/tools/omada/namespace.js.map +1 -1
  24. package/dist/version.d.ts +1 -1
  25. package/dist/version.js +1 -1
  26. package/package.json +5 -1
  27. package/.codex/review-rules.md +0 -16
  28. package/.dockerignore +0 -8
  29. package/.gitattributes +0 -30
  30. package/.github/workflows/ci.yml +0 -193
  31. package/AGENTS.md +0 -389
  32. package/Dockerfile +0 -50
  33. package/docker-compose.yml +0 -23
  34. package/docs/CHANGELOG.md +0 -341
  35. package/docs/COVERAGE.md +0 -219
  36. package/docs/QUICK_START.md +0 -240
  37. package/eslint.config.js +0 -33
  38. package/n8n/ai-agent-safety-monitor.json +0 -355
  39. package/openapi.json +0 -1259
  40. package/scripts/check-version-sync.mjs +0 -69
  41. package/scripts/generate-jwt.ts +0 -121
  42. package/src/config.ts +0 -440
  43. package/src/haClient/automations.ts +0 -261
  44. package/src/haClient/calendars.ts +0 -94
  45. package/src/haClient/config.ts +0 -52
  46. package/src/haClient/devices.ts +0 -653
  47. package/src/haClient/entities.ts +0 -128
  48. package/src/haClient/events.ts +0 -689
  49. package/src/haClient/history.ts +0 -73
  50. package/src/haClient/index.ts +0 -336
  51. package/src/haClient/request.ts +0 -142
  52. package/src/haClient/services.ts +0 -45
  53. package/src/haClient/states.ts +0 -60
  54. package/src/haClient/updates.ts +0 -58
  55. package/src/haClient/wsCommand.ts +0 -44
  56. package/src/index.ts +0 -164
  57. package/src/localAI/index.ts +0 -88
  58. package/src/localAI/prompts.ts +0 -50
  59. package/src/localAI/providers/base.ts +0 -120
  60. package/src/localAI/providers/ollama.ts +0 -78
  61. package/src/localAI/providers/openai.ts +0 -86
  62. package/src/localAI/types.ts +0 -67
  63. package/src/omadaClient/auth.ts +0 -178
  64. package/src/omadaClient/client.ts +0 -337
  65. package/src/omadaClient/device.ts +0 -118
  66. package/src/omadaClient/index.ts +0 -310
  67. package/src/omadaClient/network.ts +0 -284
  68. package/src/omadaClient/request.ts +0 -365
  69. package/src/omadaClient/security.ts +0 -51
  70. package/src/omadaClient/site.ts +0 -344
  71. package/src/ownerQuestions/service.ts +0 -536
  72. package/src/permissions/index.ts +0 -143
  73. package/src/resources/index.ts +0 -213
  74. package/src/server/auth.ts +0 -175
  75. package/src/server/chatFace.ts +0 -789
  76. package/src/server/chatSlice.ts +0 -57
  77. package/src/server/common.ts +0 -170
  78. package/src/server/eventSubscription.ts +0 -343
  79. package/src/server/http.ts +0 -626
  80. package/src/server/instructions.ts +0 -97
  81. package/src/server/rateLimiter.ts +0 -227
  82. package/src/server/stdio.ts +0 -41
  83. package/src/server/stream.ts +0 -205
  84. package/src/tools/ai/analyzeSensors.ts +0 -33
  85. package/src/tools/ai/index.ts +0 -20
  86. package/src/tools/common.ts +0 -442
  87. package/src/tools/homeassistant/askOwner.ts +0 -133
  88. package/src/tools/homeassistant/automationControls.ts +0 -66
  89. package/src/tools/homeassistant/calendar.ts +0 -97
  90. package/src/tools/homeassistant/callService.ts +0 -25
  91. package/src/tools/homeassistant/controlClimate.ts +0 -68
  92. package/src/tools/homeassistant/controlCover.ts +0 -48
  93. package/src/tools/homeassistant/controlFan.ts +0 -44
  94. package/src/tools/homeassistant/controlLight.ts +0 -38
  95. package/src/tools/homeassistant/controlMediaPlayer.ts +0 -50
  96. package/src/tools/homeassistant/createAutomation.ts +0 -35
  97. package/src/tools/homeassistant/deleteAutomation.ts +0 -24
  98. package/src/tools/homeassistant/entityAction.ts +0 -35
  99. package/src/tools/homeassistant/entityGuard.ts +0 -28
  100. package/src/tools/homeassistant/getAllSensors.ts +0 -46
  101. package/src/tools/homeassistant/getAutomationTrace.ts +0 -30
  102. package/src/tools/homeassistant/getDomainSummary.ts +0 -29
  103. package/src/tools/homeassistant/getEntitiesByDomain.ts +0 -56
  104. package/src/tools/homeassistant/getHistory.ts +0 -24
  105. package/src/tools/homeassistant/getState.ts +0 -24
  106. package/src/tools/homeassistant/getStates.ts +0 -46
  107. package/src/tools/homeassistant/getVersion.ts +0 -21
  108. package/src/tools/homeassistant/index.ts +0 -121
  109. package/src/tools/homeassistant/listAutomations.ts +0 -35
  110. package/src/tools/homeassistant/listEntities.ts +0 -44
  111. package/src/tools/homeassistant/listPersons.ts +0 -46
  112. package/src/tools/homeassistant/sceneAndScript.ts +0 -53
  113. package/src/tools/homeassistant/searchEntities.ts +0 -58
  114. package/src/tools/homeassistant/sendNotification.ts +0 -179
  115. package/src/tools/homeassistant/systemTools.ts +0 -52
  116. package/src/tools/homeassistant/triggerAutomation.ts +0 -24
  117. package/src/tools/index.ts +0 -78
  118. package/src/tools/infra/actions.ts +0 -95
  119. package/src/tools/infra/index.ts +0 -138
  120. package/src/tools/omada/blockClient.ts +0 -28
  121. package/src/tools/omada/cyclePoePort.ts +0 -32
  122. package/src/tools/omada/deleteClient.ts +0 -27
  123. package/src/tools/omada/disableClientRateLimit.ts +0 -23
  124. package/src/tools/omada/getClient.ts +0 -14
  125. package/src/tools/omada/getDevice.ts +0 -14
  126. package/src/tools/omada/getFirewallSetting.ts +0 -14
  127. package/src/tools/omada/getInternetInfo.ts +0 -14
  128. package/src/tools/omada/getLanNetworkList.ts +0 -14
  129. package/src/tools/omada/getLanProfileList.ts +0 -14
  130. package/src/tools/omada/getPortForwardingStatus.ts +0 -26
  131. package/src/tools/omada/getRateLimitProfiles.ts +0 -14
  132. package/src/tools/omada/getSiteNtpStatus.ts +0 -14
  133. package/src/tools/omada/getSsidDetail.ts +0 -22
  134. package/src/tools/omada/getSsidList.ts +0 -21
  135. package/src/tools/omada/getSwitchStackDetail.ts +0 -14
  136. package/src/tools/omada/getThreatList.ts +0 -42
  137. package/src/tools/omada/getWlanGroupList.ts +0 -14
  138. package/src/tools/omada/graph.ts +0 -187
  139. package/src/tools/omada/index.ts +0 -153
  140. package/src/tools/omada/listBlockedClients.ts +0 -17
  141. package/src/tools/omada/listClients.ts +0 -14
  142. package/src/tools/omada/listClientsActivity.ts +0 -25
  143. package/src/tools/omada/listClientsPastConnections.ts +0 -44
  144. package/src/tools/omada/listDevices.ts +0 -14
  145. package/src/tools/omada/listDevicesStats.ts +0 -41
  146. package/src/tools/omada/listMostActiveClients.ts +0 -14
  147. package/src/tools/omada/listSites.ts +0 -18
  148. package/src/tools/omada/namespace.ts +0 -1164
  149. package/src/tools/omada/searchDevices.ts +0 -20
  150. package/src/tools/omada/setClientRateLimit.ts +0 -26
  151. package/src/tools/omada/setClientRateLimitProfile.ts +0 -25
  152. package/src/tools/omada/setSiteNtpServers.ts +0 -36
  153. package/src/tools/omada/setSsidEnabled.ts +0 -38
  154. package/src/tools/omada/unblockClient.ts +0 -26
  155. package/src/tools/types.ts +0 -11
  156. package/src/tools/utils/pagination-schema.ts +0 -27
  157. package/src/types/ai/index.ts +0 -11
  158. package/src/types/ai/types.ts +0 -67
  159. package/src/types/homeassistant/automations.ts +0 -43
  160. package/src/types/homeassistant/calendars.ts +0 -26
  161. package/src/types/homeassistant/config.ts +0 -14
  162. package/src/types/homeassistant/entities.ts +0 -14
  163. package/src/types/homeassistant/errors.ts +0 -33
  164. package/src/types/homeassistant/haConfig.ts +0 -7
  165. package/src/types/homeassistant/history.ts +0 -13
  166. package/src/types/homeassistant/index.ts +0 -46
  167. package/src/types/homeassistant/request.ts +0 -6
  168. package/src/types/homeassistant/serviceCall.ts +0 -18
  169. package/src/types/homeassistant/states.ts +0 -58
  170. package/src/types/homeassistant/updates.ts +0 -28
  171. package/src/types/index.ts +0 -12
  172. package/src/types/omada/activeClientInfo.ts +0 -17
  173. package/src/types/omada/clientActivity.ts +0 -32
  174. package/src/types/omada/clientBlockStatus.ts +0 -11
  175. package/src/types/omada/clientPastConnection.ts +0 -82
  176. package/src/types/omada/index.ts +0 -14
  177. package/src/types/omada/omadaApiResponse.ts +0 -5
  178. package/src/types/omada/omadaClientInfo.ts +0 -11
  179. package/src/types/omada/omadaDeviceInfo.ts +0 -11
  180. package/src/types/omada/omadaDeviceStats.ts +0 -31
  181. package/src/types/omada/omadaSiteSummary.ts +0 -5
  182. package/src/types/omada/oswStackDetail.ts +0 -11
  183. package/src/types/omada/paginatedResult.ts +0 -6
  184. package/src/types/omada/rateLimitProfile.ts +0 -58
  185. package/src/types/omada/threatInfo.ts +0 -38
  186. package/src/types/omada/tokenResult.ts +0 -6
  187. package/src/types.ts +0 -2
  188. package/src/utils/config-validations.ts +0 -68
  189. package/src/utils/jwt.ts +0 -112
  190. package/src/utils/logger.ts +0 -121
  191. package/src/utils/plainText.ts +0 -48
  192. package/src/utils/redact.ts +0 -59
  193. package/src/utils/sanitizeError.ts +0 -18
  194. package/src/utils/tlsDispatcher.ts +0 -69
  195. package/src/version.ts +0 -5
  196. package/tests/bdd/automation-creation.spec.ts +0 -214
  197. package/tests/bdd/automation-management.spec.ts +0 -267
  198. package/tests/bdd/calendar.spec.ts +0 -190
  199. package/tests/bdd/configuration.spec.ts +0 -283
  200. package/tests/bdd/device-control.spec.ts +0 -344
  201. package/tests/bdd/entity-listing.spec.ts +0 -208
  202. package/tests/bdd/entity-search.spec.ts +0 -183
  203. package/tests/bdd/history-retrieval.spec.ts +0 -195
  204. package/tests/bdd/notifications.spec.ts +0 -238
  205. package/tests/bdd/service-calls.spec.ts +0 -144
  206. package/tests/bdd/state-retrieval.spec.ts +0 -204
  207. package/tests/bench/hotpaths.bench.ts +0 -68
  208. package/tests/config.test.ts +0 -355
  209. package/tests/features/automation-creation.feature +0 -36
  210. package/tests/features/automation-management.feature +0 -54
  211. package/tests/features/calendar.feature +0 -37
  212. package/tests/features/configuration.feature +0 -70
  213. package/tests/features/device-control.feature +0 -100
  214. package/tests/features/entity-listing.feature +0 -51
  215. package/tests/features/entity-search.feature +0 -44
  216. package/tests/features/history-retrieval.feature +0 -38
  217. package/tests/features/notifications.feature +0 -37
  218. package/tests/features/service-calls.feature +0 -35
  219. package/tests/features/state-retrieval.feature +0 -37
  220. package/tests/fixtures/openapi-default-slice.json +0 -290
  221. package/tests/haClient/automationTrace.test.ts +0 -59
  222. package/tests/haClient/automations.test.ts +0 -149
  223. package/tests/haClient/config.test.ts +0 -117
  224. package/tests/haClient/entities.test.ts +0 -188
  225. package/tests/haClient/events.test.ts +0 -455
  226. package/tests/haClient/eventsTls.test.ts +0 -52
  227. package/tests/haClient/history.test.ts +0 -228
  228. package/tests/haClient/index.test.ts +0 -243
  229. package/tests/haClient/request.test.ts +0 -217
  230. package/tests/haClient/services.test.ts +0 -120
  231. package/tests/haClient/states.test.ts +0 -119
  232. package/tests/haClient/updates.test.ts +0 -193
  233. package/tests/index.test.ts +0 -160
  234. package/tests/integration/homeassistant.test.ts +0 -144
  235. package/tests/integration/omada.test.ts +0 -167
  236. package/tests/integration/server.test.ts +0 -151
  237. package/tests/localAI/index.test.ts +0 -177
  238. package/tests/localAI/ollama.test.ts +0 -187
  239. package/tests/localAI/openai.test.ts +0 -257
  240. package/tests/omadaClient/actions.test.ts +0 -161
  241. package/tests/omadaClient/clientBlocks.test.ts +0 -170
  242. package/tests/omadaClient/ntp.test.ts +0 -381
  243. package/tests/omadaClient/site.test.ts +0 -37
  244. package/tests/ownerQuestions/service.test.ts +0 -499
  245. package/tests/ownerQuestions/tools.test.ts +0 -186
  246. package/tests/permissions/index.test.ts +0 -217
  247. package/tests/resources/index.test.ts +0 -288
  248. package/tests/scripts/generateJwt.test.ts +0 -44
  249. package/tests/security/accessControl.test.ts +0 -223
  250. package/tests/security/authentication.test.ts +0 -117
  251. package/tests/security/configuration.test.ts +0 -170
  252. package/tests/security/cryptoTransport.test.ts +0 -172
  253. package/tests/security/dependencies.test.ts +0 -152
  254. package/tests/security/injection.test.ts +0 -117
  255. package/tests/security/insecureDesign.test.ts +0 -121
  256. package/tests/security/logging.test.ts +0 -131
  257. package/tests/security/resourceConsumption.test.ts +0 -142
  258. package/tests/server/auth.test.ts +0 -249
  259. package/tests/server/chatFace.test.ts +0 -320
  260. package/tests/server/common.test.ts +0 -84
  261. package/tests/server/http.test.ts +0 -561
  262. package/tests/server/instructions.test.ts +0 -152
  263. package/tests/server/memoizeJsonSchema.test.ts +0 -71
  264. package/tests/server/stdio.test.ts +0 -99
  265. package/tests/server/stream.test.ts +0 -285
  266. package/tests/server/toolVisibility.http.test.ts +0 -111
  267. package/tests/server/toolVisibility.test.ts +0 -129
  268. package/tests/tools/ai/analyzeSensors.test.ts +0 -227
  269. package/tests/tools/common.test.ts +0 -123
  270. package/tests/tools/homeassistant/handlers.test.ts +0 -872
  271. package/tests/tools/infra/invokeAction.test.ts +0 -280
  272. package/tests/tools/omada/graph.test.ts +0 -385
  273. package/tests/tools/omada/handlers.test.ts +0 -897
  274. package/tests/tools/omada/namespace.test.ts +0 -483
  275. package/tests/tools/registry.test.ts +0 -168
  276. package/tests/utils/config-validations.test.ts +0 -352
  277. package/tests/utils/jwt.test.ts +0 -115
  278. package/tests/utils/logger.test.ts +0 -327
  279. package/tests/utils/redact.test.ts +0 -105
  280. package/tsconfig.json +0 -24
  281. package/vitest.config.ts +0 -38
@@ -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
- }
@@ -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
- }