een-api-toolkit 0.3.85 → 0.3.97

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 (51) hide show
  1. package/.claude/agents/api-coverage-agent.md +71 -29
  2. package/.claude/agents/een-devices-agent.md +21 -0
  3. package/.claude/agents/een-ptz-agent.md +245 -0
  4. package/CHANGELOG.md +60 -40
  5. package/dist/index.cjs +3 -3
  6. package/dist/index.cjs.map +1 -1
  7. package/dist/index.d.ts +412 -0
  8. package/dist/index.js +1079 -951
  9. package/dist/index.js.map +1 -1
  10. package/docs/AI-CONTEXT.md +3 -1
  11. package/docs/ai-reference/AI-AUTH.md +1 -1
  12. package/docs/ai-reference/AI-AUTOMATIONS.md +1 -1
  13. package/docs/ai-reference/AI-DEVICES.md +12 -1
  14. package/docs/ai-reference/AI-EVENT-DATA-SCHEMAS.md +1 -1
  15. package/docs/ai-reference/AI-EVENTS.md +1 -1
  16. package/docs/ai-reference/AI-GROUPING.md +1 -1
  17. package/docs/ai-reference/AI-JOBS.md +1 -1
  18. package/docs/ai-reference/AI-MEDIA.md +1 -1
  19. package/docs/ai-reference/AI-PTZ.md +174 -0
  20. package/docs/ai-reference/AI-SETUP.md +1 -1
  21. package/docs/ai-reference/AI-USERS.md +1 -1
  22. package/examples/vue-ptz/.env.example +4 -0
  23. package/examples/vue-ptz/README.md +221 -0
  24. package/examples/vue-ptz/e2e/app.spec.ts +58 -0
  25. package/examples/vue-ptz/e2e/auth.spec.ts +296 -0
  26. package/examples/vue-ptz/index.html +13 -0
  27. package/examples/vue-ptz/package-lock.json +1729 -0
  28. package/examples/vue-ptz/package.json +29 -0
  29. package/examples/vue-ptz/playwright.config.ts +49 -0
  30. package/examples/vue-ptz/screenshot-ptz.png +0 -0
  31. package/examples/vue-ptz/src/App.vue +154 -0
  32. package/examples/vue-ptz/src/components/ApiLog.vue +387 -0
  33. package/examples/vue-ptz/src/components/CameraSelector.vue +155 -0
  34. package/examples/vue-ptz/src/components/DirectionPad.vue +350 -0
  35. package/examples/vue-ptz/src/components/LiveVideoPlayer.vue +248 -0
  36. package/examples/vue-ptz/src/components/PositionDisplay.vue +206 -0
  37. package/examples/vue-ptz/src/components/PositionInput.vue +190 -0
  38. package/examples/vue-ptz/src/components/PresetManager.vue +538 -0
  39. package/examples/vue-ptz/src/composables/useApiLog.ts +89 -0
  40. package/examples/vue-ptz/src/main.ts +22 -0
  41. package/examples/vue-ptz/src/router/index.ts +61 -0
  42. package/examples/vue-ptz/src/views/Callback.vue +76 -0
  43. package/examples/vue-ptz/src/views/Home.vue +199 -0
  44. package/examples/vue-ptz/src/views/Login.vue +32 -0
  45. package/examples/vue-ptz/src/views/Logout.vue +59 -0
  46. package/examples/vue-ptz/src/views/PtzControl.vue +173 -0
  47. package/examples/vue-ptz/src/vite-env.d.ts +12 -0
  48. package/examples/vue-ptz/tsconfig.json +21 -0
  49. package/examples/vue-ptz/tsconfig.node.json +11 -0
  50. package/examples/vue-ptz/vite.config.ts +12 -0
  51. package/package.json +1 -1
@@ -0,0 +1,296 @@
1
+ /**
2
+ * E2E tests for the Vue PTZ Example - OAuth Login Flow
3
+ *
4
+ * Tests the OAuth login flow through the UI:
5
+ * 1. Click login button in the example app
6
+ * 2. Enter credentials on EEN OAuth page
7
+ * 3. Complete the OAuth callback
8
+ * 4. Verify authenticated state and PTZ control functionality
9
+ *
10
+ * Required environment variables:
11
+ * - VITE_PROXY_URL: OAuth proxy URL (e.g., http://127.0.0.1:8787)
12
+ * - VITE_EEN_CLIENT_ID: EEN OAuth client ID
13
+ * - TEST_USER: Test user email
14
+ * - TEST_PASSWORD: Test user password
15
+ *
16
+ * Note: Helper functions (isProxyAccessible, performLogin, clearAuthState) are
17
+ * intentionally duplicated in each example's auth.spec.ts to avoid Playwright's
18
+ * "Requiring @playwright/test second time" error that occurs when importing
19
+ * from a shared file outside the example directory.
20
+ */
21
+
22
+ import { test, expect, Page } from '@playwright/test'
23
+ import { baseURL } from '../playwright.config'
24
+
25
+ const TIMEOUTS = {
26
+ OAUTH_REDIRECT: 30000,
27
+ ELEMENT_VISIBLE: 15000,
28
+ PASSWORD_VISIBLE: 10000,
29
+ AUTH_COMPLETE: 30000,
30
+ UI_UPDATE: 10000,
31
+ PROXY_CHECK: 5000,
32
+ PTZ_LOAD: 30000
33
+ } as const
34
+
35
+ const TEST_USER = process.env.TEST_USER
36
+ const TEST_PASSWORD = process.env.TEST_PASSWORD
37
+ const PROXY_URL = process.env.VITE_PROXY_URL
38
+
39
+ /**
40
+ * Checks if the OAuth proxy server is accessible.
41
+ * Returns false if proxy is unavailable, allowing tests to be skipped gracefully.
42
+ */
43
+ async function isProxyAccessible(): Promise<boolean> {
44
+ if (!PROXY_URL) return false
45
+ const controller = new AbortController()
46
+ const timeoutId = setTimeout(() => controller.abort(), TIMEOUTS.PROXY_CHECK)
47
+
48
+ try {
49
+ const response = await fetch(PROXY_URL, {
50
+ method: 'HEAD',
51
+ signal: controller.signal
52
+ })
53
+ return response.ok || response.status === 404
54
+ } catch (error) {
55
+ if (!process.env.CI) {
56
+ console.log('Proxy check failed:', error instanceof Error ? error.message : error)
57
+ }
58
+ return false
59
+ } finally {
60
+ clearTimeout(timeoutId)
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Performs OAuth login flow through the EEN authentication page.
66
+ * Handles two-step navigation: app login page -> EEN OAuth -> callback
67
+ */
68
+ async function performLogin(page: Page, username: string, password: string): Promise<void> {
69
+ await page.goto('/')
70
+
71
+ // Click login button on home page to go to login page
72
+ await page.click('[data-testid="login-button"]')
73
+ await page.waitForURL('/login')
74
+
75
+ // Click login button on login page to trigger OAuth
76
+ await Promise.all([
77
+ page.waitForURL(/.*eagleeyenetworks.com.*/, { timeout: TIMEOUTS.OAUTH_REDIRECT }),
78
+ page.getByRole('button', { name: 'Login with Eagle Eye Networks' }).click()
79
+ ])
80
+
81
+ // EEN OAuth page selectors - these depend on EEN's login UI and may need
82
+ // updates if EEN changes their authentication page structure
83
+ const emailInput = page.locator('#authentication--input__email')
84
+ await emailInput.waitFor({ state: 'visible', timeout: TIMEOUTS.ELEMENT_VISIBLE })
85
+ await emailInput.fill(username)
86
+
87
+ await page.getByRole('button', { name: 'Next' }).click()
88
+
89
+ const passwordInput = page.locator('#authentication--input__password')
90
+ await passwordInput.waitFor({ state: 'visible', timeout: TIMEOUTS.PASSWORD_VISIBLE })
91
+ await passwordInput.fill(password)
92
+
93
+ // EEN uses either #next or "Sign in" button depending on login flow
94
+ await page.locator('#next, button:has-text("Sign in")').first().click()
95
+
96
+ // Wait for redirect back to the app using configured baseURL
97
+ const baseURLPattern = new RegExp(baseURL.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
98
+ await page.waitForURL(baseURLPattern, { timeout: TIMEOUTS.AUTH_COMPLETE })
99
+ }
100
+
101
+ /**
102
+ * Clears authentication state from browser storage.
103
+ * Used in afterEach to ensure test isolation.
104
+ */
105
+ async function clearAuthState(page: Page): Promise<void> {
106
+ try {
107
+ const url = page.url()
108
+ if (url && url.startsWith('http')) {
109
+ await page.evaluate(() => {
110
+ try {
111
+ localStorage.clear()
112
+ sessionStorage.clear()
113
+ } catch {
114
+ // Storage access may fail in certain contexts
115
+ }
116
+ })
117
+ }
118
+ } catch (error) {
119
+ if (!process.env.CI) {
120
+ console.log('Clear auth state failed:', error instanceof Error ? error.message : error)
121
+ }
122
+ }
123
+ }
124
+
125
+ test.describe('Vue PTZ Example - Auth', () => {
126
+ let proxyAccessible = false
127
+
128
+ function skipIfNoProxy() {
129
+ test.skip(!proxyAccessible, 'OAuth proxy not accessible')
130
+ }
131
+
132
+ function skipIfNoCredentials() {
133
+ test.skip(!TEST_USER || !TEST_PASSWORD, 'Test credentials not available')
134
+ }
135
+
136
+ test.beforeAll(async () => {
137
+ proxyAccessible = await isProxyAccessible()
138
+ if (!proxyAccessible) {
139
+ console.log('OAuth proxy not accessible - OAuth tests will be skipped')
140
+ }
141
+ })
142
+
143
+ test.afterEach(async ({ page }) => {
144
+ await clearAuthState(page)
145
+ })
146
+
147
+ test('shows login button when not authenticated', async ({ page }) => {
148
+ await page.goto('/')
149
+ await expect(page.getByTestId('not-authenticated')).toBeVisible()
150
+ await expect(page.getByTestId('nav-login')).toBeVisible()
151
+ })
152
+
153
+ test('ptz page redirects to login when not authenticated', async ({ page }) => {
154
+ await page.goto('/ptz')
155
+ await expect(page).toHaveURL('/login')
156
+ })
157
+
158
+ test('login button redirects to OAuth page', async ({ page }) => {
159
+ skipIfNoProxy()
160
+ skipIfNoCredentials()
161
+
162
+ await page.goto('/')
163
+ await expect(page.getByTestId('login-button')).toBeVisible()
164
+
165
+ // Click login button on home page to go to login page
166
+ await page.click('[data-testid="login-button"]')
167
+ await page.waitForURL('/login')
168
+
169
+ // Click login button on login page to trigger OAuth
170
+ await Promise.all([
171
+ page.waitForURL(/.*eagleeyenetworks.com.*/, { timeout: TIMEOUTS.OAUTH_REDIRECT }),
172
+ page.getByRole('button', { name: 'Login with Eagle Eye Networks' }).click()
173
+ ])
174
+
175
+ // EEN OAuth page selector
176
+ const emailInput = page.locator('#authentication--input__email')
177
+ await expect(emailInput).toBeVisible({ timeout: TIMEOUTS.ELEMENT_VISIBLE })
178
+ })
179
+
180
+ test('complete OAuth login flow and view PTZ controls', async ({ page }) => {
181
+ skipIfNoProxy()
182
+ skipIfNoCredentials()
183
+
184
+ await page.goto('/')
185
+ await expect(page.getByTestId('not-authenticated')).toBeVisible()
186
+
187
+ await performLogin(page, TEST_USER!, TEST_PASSWORD!)
188
+
189
+ await expect(page.getByTestId('not-authenticated')).not.toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
190
+ await expect(page.getByTestId('nav-ptz')).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
191
+ await expect(page.getByTestId('nav-logout')).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
192
+ })
193
+
194
+ test('can view PTZ controls after login', async ({ page }) => {
195
+ skipIfNoProxy()
196
+ skipIfNoCredentials()
197
+
198
+ await performLogin(page, TEST_USER!, TEST_PASSWORD!)
199
+ await expect(page.getByTestId('nav-ptz')).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
200
+
201
+ await page.click('[data-testid="nav-ptz"]')
202
+ await page.waitForURL('/ptz')
203
+
204
+ await expect(page.getByRole('heading', { name: 'PTZ Camera Control' })).toBeVisible()
205
+
206
+ // Wait for camera selector or no-cameras message
207
+ await page.waitForSelector('[data-testid="ptz-camera-select"], [data-testid="no-ptz-cameras"]', {
208
+ timeout: TIMEOUTS.PTZ_LOAD
209
+ })
210
+ })
211
+
212
+ test('PTZ page shows direction pad and controls', async ({ page }) => {
213
+ skipIfNoProxy()
214
+ skipIfNoCredentials()
215
+
216
+ await performLogin(page, TEST_USER!, TEST_PASSWORD!)
217
+ await expect(page.getByTestId('nav-ptz')).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
218
+
219
+ await page.click('[data-testid="nav-ptz"]')
220
+ await page.waitForURL('/ptz')
221
+
222
+ // PTZ layout should be visible
223
+ await expect(page.getByTestId('ptz-layout')).toBeVisible()
224
+
225
+ // Direction pad should be visible
226
+ await expect(page.getByTestId('direction-pad')).toBeVisible()
227
+
228
+ // Position display should be visible
229
+ await expect(page.getByTestId('position-display')).toBeVisible()
230
+
231
+ // Preset manager should be visible
232
+ await expect(page.getByTestId('preset-manager')).toBeVisible()
233
+ })
234
+
235
+ test('exercises PTZ API calls when PTZ camera is available', async ({ page }) => {
236
+ skipIfNoProxy()
237
+ skipIfNoCredentials()
238
+
239
+ await performLogin(page, TEST_USER!, TEST_PASSWORD!)
240
+ await expect(page.getByTestId('nav-ptz')).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
241
+
242
+ await page.click('[data-testid="nav-ptz"]')
243
+ await page.waitForURL('/ptz')
244
+
245
+ // Wait for camera loading to finish
246
+ await page.waitForSelector('[data-testid="ptz-camera-select"], [data-testid="no-ptz-cameras"]', {
247
+ timeout: TIMEOUTS.PTZ_LOAD
248
+ })
249
+
250
+ // If no PTZ cameras available, skip gracefully
251
+ const noPtzCameras = await page.getByTestId('no-ptz-cameras').isVisible().catch(() => false)
252
+ if (noPtzCameras) {
253
+ console.log('No PTZ cameras available — skipping PTZ API tests')
254
+ return
255
+ }
256
+
257
+ // PTZ camera is available — exercise API calls
258
+
259
+ // 1. Verify position display shows numeric values (confirms getPtzPosition succeeded)
260
+ const positionX = page.getByTestId('position-x')
261
+ await expect(positionX).toBeVisible({ timeout: TIMEOUTS.PTZ_LOAD })
262
+ await expect(positionX).not.toHaveText('--', { timeout: TIMEOUTS.PTZ_LOAD })
263
+
264
+ const positionY = page.getByTestId('position-y')
265
+ await expect(positionY).toBeVisible()
266
+ await expect(positionY).not.toHaveText('--')
267
+
268
+ const positionZ = page.getByTestId('position-z')
269
+ await expect(positionZ).toBeVisible()
270
+ await expect(positionZ).not.toHaveText('--')
271
+
272
+ // 2. Click a direction button and verify position still reads numeric (confirms movePtz round-trip)
273
+ await page.click('[data-testid="btn-up"]')
274
+ await page.waitForTimeout(2000)
275
+ await page.click('[data-testid="refresh-position"]')
276
+ await expect(positionX).not.toHaveText('--', { timeout: TIMEOUTS.UI_UPDATE })
277
+ await expect(positionY).not.toHaveText('--', { timeout: TIMEOUTS.UI_UPDATE })
278
+
279
+ // 3. Verify preset manager loaded (confirms getPtzSettings succeeded)
280
+ await expect(page.getByTestId('preset-manager')).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
281
+ })
282
+
283
+ test('can logout after login', async ({ page }) => {
284
+ skipIfNoProxy()
285
+ skipIfNoCredentials()
286
+
287
+ await performLogin(page, TEST_USER!, TEST_PASSWORD!)
288
+ await expect(page.getByTestId('nav-logout')).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
289
+
290
+ await page.click('[data-testid="nav-logout"]')
291
+
292
+ await page.waitForURL('**/')
293
+ await expect(page.getByTestId('not-authenticated')).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
294
+ await expect(page.getByTestId('nav-login')).toBeVisible()
295
+ })
296
+ })
@@ -0,0 +1,13 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>EEN PTZ Control Example</title>
8
+ </head>
9
+ <body>
10
+ <div id="app"></div>
11
+ <script type="module" src="/src/main.ts"></script>
12
+ </body>
13
+ </html>