@coffeerunhobby/mcp-ha-connect 2.2.1 → 2.4.0

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