een-api-toolkit 0.3.79 → 0.3.81

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.
@@ -254,22 +254,24 @@ The EEN OAuth login page uses a **two-step authentication flow**:
254
254
  1. **Step 1 - Email**: User enters email address and clicks "Next"
255
255
  2. **Step 2 - Password**: Password field appears, user enters password and clicks "Sign in"
256
256
 
257
- This is important for Playwright tests - you cannot fill both fields at once:
257
+ This is important for Playwright tests - you cannot fill both fields at once.
258
+
259
+ **BEST PRACTICE:** Use `getByPlaceholder()` selectors — they are the most reliable across EEN login page versions:
258
260
 
259
261
  ```typescript
260
262
  // Playwright test example for EEN two-step login
261
263
  // Step 1: Enter email and click Next
262
- const emailInput = page.locator('input[type="email"], input[type="text"]').first()
263
- await emailInput.fill(TEST_USER)
264
+ await page.getByPlaceholder(/email/i).fill(TEST_USER)
264
265
  await page.getByRole('button', { name: /next/i }).click()
265
266
 
266
267
  // Step 2: Wait for password field and fill it
267
- const passwordInput = page.locator('input[type="password"]')
268
- await passwordInput.waitFor({ state: 'visible', timeout: 10000 })
269
- await passwordInput.fill(TEST_PASSWORD)
270
- await page.getByRole('button', { name: /sign in/i }).click()
268
+ await page.getByPlaceholder(/password/i).fill(TEST_PASSWORD, { timeout: 15000 })
269
+ await page.getByRole('button', { name: /sign in|next/i }).click()
271
270
  ```
272
271
 
272
+ **NOTE:** The `#authentication--input__email` and `#authentication--input__password` ID selectors
273
+ may not always be present. The `getByPlaceholder()` approach is more reliable.
274
+
273
275
  ## Playwright E2E Test Patterns
274
276
 
275
277
  **Reference examples in:** `node_modules/een-api-toolkit/examples/*/e2e/auth.spec.ts`
@@ -283,27 +285,22 @@ Best practices for auth testing:
283
285
  ```typescript
284
286
  // Complete performLogin helper function
285
287
  async function performLogin(page: Page, username: string, password: string): Promise<void> {
286
- await page.goto('/login')
287
- await page.click('button:has-text("Login with Eagle Eye Networks")')
288
+ await page.goto('/')
289
+ await page.getByRole('link', { name: 'Login' }).click()
288
290
 
289
291
  // Wait for EEN OAuth page
290
- await page.waitForURL(/.*eagleeyenetworks.com.*/, { timeout: 30000 })
292
+ await page.waitForURL(/auth\.eagleeyenetworks\.com|id\.eagleeyenetworks\.com/, { timeout: 15000 })
291
293
 
292
- // Step 1: Email
293
- const emailInput = page.locator('#authentication--input__email, input[type="email"]').first()
294
- await emailInput.waitFor({ state: 'visible', timeout: 15000 })
295
- await emailInput.fill(username)
296
- await page.getByRole('button', { name: 'Next' }).click()
294
+ // Step 1: Email (use getByPlaceholder - most reliable selector)
295
+ await page.getByPlaceholder(/email/i).fill(username)
296
+ await page.getByRole('button', { name: /next/i }).click()
297
297
 
298
- // Step 2: Password
299
- const passwordInput = page.locator('#authentication--input__password, input[type="password"]')
300
- await passwordInput.waitFor({ state: 'visible', timeout: 10000 })
301
- await passwordInput.fill(password)
302
- await page.locator('#next, button:has-text("Sign in")').first().click()
298
+ // Step 2: Password (appears after Next is clicked)
299
+ await page.getByPlaceholder(/password/i).fill(password, { timeout: 15000 })
300
+ await page.getByRole('button', { name: /sign in|next/i }).click()
303
301
 
304
302
  // Wait for redirect back to app
305
- await page.waitForURL(/127\.0\.0\.1:3333/, { timeout: 60000 })
306
- await page.waitForURL('**/', { timeout: 60000 })
303
+ await page.waitForURL('http://127.0.0.1:3333/**', { timeout: 30000 })
307
304
  }
308
305
 
309
306
  // Clear auth state helper
@@ -381,22 +381,32 @@ async function fetchNotifications() {
381
381
  4. **Cleanup** - Delete subscription when done (or it auto-expires after 15 min of inactivity)
382
382
 
383
383
  ### createEventSubscription()
384
+
385
+ **CRITICAL: The subscription API uses nested `deliveryConfig` and `filters` structures, NOT flat params.**
386
+
384
387
  ```typescript
388
+ import { ref, onUnmounted } from 'vue'
385
389
  import {
386
390
  createEventSubscription,
387
391
  connectToEventSubscription,
388
392
  deleteEventSubscription,
389
- type CreateEventSubscriptionParams
393
+ type SSEEvent,
394
+ type SSEConnection,
395
+ type SSEConnectionStatus
390
396
  } from 'een-api-toolkit'
391
397
 
398
+ const sseConnection = ref<SSEConnection | null>(null)
392
399
  const subscriptionId = ref<string | null>(null)
393
- const sseConnection = ref<EventSource | null>(null)
394
400
 
395
- async function startRealTimeEvents(cameraId: string) {
401
+ async function startRealTimeEvents(cameraId: string, eventTypes: string[]) {
396
402
  // Step 1: Create subscription
403
+ // IMPORTANT: types must be objects with { id: string }, not plain strings
397
404
  const result = await createEventSubscription({
398
- actors: [`camera:${cameraId}`],
399
- types: ['een.motionDetectionEvent.v1', 'een.objectDetectionEvent.v1']
405
+ deliveryConfig: { type: 'serverSentEvents.v1' },
406
+ filters: [{
407
+ actors: [`camera:${cameraId}`],
408
+ types: eventTypes.map(t => ({ id: t }))
409
+ }]
400
410
  })
401
411
 
402
412
  if (result.error) {
@@ -406,34 +416,54 @@ async function startRealTimeEvents(cameraId: string) {
406
416
 
407
417
  subscriptionId.value = result.data.id
408
418
 
409
- // Step 2: Connect to SSE stream
410
- const connection = connectToEventSubscription(result.data.sseUrl, {
411
- onEvent: (event) => {
412
- console.log('Real-time event:', event)
413
- // Add to events list, show notification, etc.
419
+ // Step 2: Extract SSE URL from deliveryConfig (NOT from result.data.sseUrl)
420
+ const sseUrl = result.data.deliveryConfig.type === 'serverSentEvents.v1'
421
+ ? result.data.deliveryConfig.sseUrl
422
+ : undefined
423
+
424
+ if (!sseUrl) return
425
+
426
+ // Step 3: Connect to SSE stream
427
+ // IMPORTANT: Returns a Result type { data, error }, NOT the connection directly
428
+ const connectionResult = connectToEventSubscription(sseUrl, {
429
+ onEvent: (event: SSEEvent) => {
430
+ console.log('Real-time event:', event.type, event.startTimestamp)
414
431
  },
415
- onError: (error) => {
416
- console.error('SSE error:', error)
432
+ onError: (err: Error) => {
433
+ console.error('SSE error:', err.message)
417
434
  },
418
- onOpen: () => {
419
- console.log('SSE connection opened')
435
+ onStatusChange: (status: SSEConnectionStatus) => {
436
+ // status: 'connected' | 'connecting' | 'disconnected' | 'error'
437
+ console.log('SSE status:', status)
420
438
  }
421
439
  })
422
440
 
423
- sseConnection.value = connection
441
+ if (connectionResult.error) {
442
+ console.error('Failed to connect:', connectionResult.error.message)
443
+ return
444
+ }
445
+
446
+ sseConnection.value = connectionResult.data
424
447
  }
425
448
 
426
- // Cleanup when component unmounts
427
- onUnmounted(async () => {
428
- // Close SSE connection
449
+ // Cleanup when component unmounts or camera changes
450
+ async function cleanupSSE() {
429
451
  if (sseConnection.value) {
430
452
  sseConnection.value.close()
453
+ sseConnection.value = null
431
454
  }
432
-
433
- // Delete subscription
434
455
  if (subscriptionId.value) {
435
- await deleteEventSubscription(subscriptionId.value)
456
+ try {
457
+ await deleteEventSubscription(subscriptionId.value)
458
+ } catch {
459
+ // Ignore cleanup errors
460
+ }
461
+ subscriptionId.value = null
436
462
  }
463
+ }
464
+
465
+ onUnmounted(() => {
466
+ cleanupSSE()
437
467
  })
438
468
  ```
439
469
 
@@ -568,9 +598,21 @@ async function createMetricsChart(canvas: HTMLCanvasElement, cameraId: string) {
568
598
  | SUBSCRIPTION_LIMIT | Too many subscriptions | Delete old subscriptions |
569
599
  | SSE_CONNECTION_FAILED | Can't connect to stream | Retry with backoff |
570
600
 
601
+ ## Common SSE Mistakes
602
+
603
+ | Mistake | Correct Approach |
604
+ |---------|-----------------|
605
+ | `createEventSubscription({ actors, types })` flat params | Use `{ deliveryConfig: { type: 'serverSentEvents.v1' }, filters: [{ actors, types }] }` |
606
+ | `types: ['een.motionDetectionEvent.v1']` as strings | Use `types: [{ id: 'een.motionDetectionEvent.v1' }]` as objects |
607
+ | `result.data.sseUrl` for the SSE URL | Use `result.data.deliveryConfig.sseUrl` |
608
+ | Treating `connectToEventSubscription()` return as the connection | Returns `{ data: SSEConnection, error }` Result type |
609
+ | Using `onOpen` callback | Use `onStatusChange` with `SSEConnectionStatus` type |
610
+ | Forgetting to clean up when subscribed resource changes | Always clean up existing subscription before starting a new one |
611
+
571
612
  ## Constraints
572
613
  - Always use actor format: `camera:{cameraId}` or `account:{accountId}`
573
- - Always clean up SSE subscriptions on component unmount
614
+ - Always clean up SSE subscriptions on component unmount and when the subscribed resource changes
574
615
  - Use formatTimestamp() for all timestamp parameters
575
616
  - Include 'data.overlays' in include[] to get bounding box SVGs
576
617
  - Handle SSE reconnection for long-running streams
618
+ - Use `listEventFieldValues()` to discover available event types before subscribing
@@ -67,13 +67,17 @@ assistant: "I'll use the een-media-agent to diagnose the HLS configuration and a
67
67
  - Check authentication before media operations
68
68
  - Pass config to LivePlayer's `start()` method, NOT the constructor
69
69
 
70
- ## Choosing the Right Preview Method
70
+ ## Choosing the Right Video Method
71
+
72
+ **IMPORTANT DECISION RULE:** When the user asks for "HD video", "main video", "full quality",
73
+ or "live video feed", you MUST use the **Live Video SDK** (`@een/live-video-web-sdk`).
74
+ Only use `multipartUrl` when explicitly asked for "preview", "thumbnail", or "low bandwidth" options.
71
75
 
72
76
  | Use Case | Method | Why |
73
77
  |----------|--------|-----|
74
78
  | Grid of 20+ cameras | `getLiveImage()` | Lower bandwidth, manual refresh |
75
79
  | Auto-updating preview | `multipartUrl` + `initMediaSession()` | Automatic updates, higher bandwidth |
76
- | Full-quality live video | Live Video SDK | Full resolution, lowest latency |
80
+ | **Full-quality live video** | **Live Video SDK** | **Full resolution, lowest latency** |
77
81
  | Recorded video playback | HLS via `listMedia()` | Seek capability, standard player |
78
82
 
79
83
  **CRITICAL: Main feeds do NOT support multipartUrl**
@@ -83,7 +87,8 @@ The EEN API only returns `multipartUrl` for **preview feeds** (`type: 'preview'`
83
87
  - **Preview feeds** → Use `multipartUrl` (MJPEG in `<img>` element)
84
88
  - **Main feeds** → Use **Live Video SDK** (full HD in `<video>` element)
85
89
 
86
- If you need HD quality video, you MUST use the Live Video SDK. Do not attempt to use `multipartUrl` with main feeds - it won't work.
90
+ If you need HD quality video, you MUST use the Live Video SDK (`npm install @een/live-video-web-sdk`).
91
+ Do not attempt to use `multipartUrl` with main feeds - it won't work.
87
92
 
88
93
  ## Key Functions
89
94
 
@@ -312,6 +317,52 @@ The video element MUST be rendered in the DOM before calling `player.start()`.
312
317
  </style>
313
318
  ```
314
319
 
320
+ ## CRITICAL: Camera Switching with LivePlayer
321
+
322
+ The LivePlayer SDK does **NOT cleanly release** the `<video>` element when `stop()` is called.
323
+ Reusing the same `<video>` element for a new LivePlayer instance will result in the video feed
324
+ **not updating** when the user switches cameras.
325
+
326
+ **Solution:** Use a Vue `:key` to force a fresh `<video>` element on each camera switch:
327
+
328
+ ```vue
329
+ <script setup lang="ts">
330
+ const videoRef = ref<HTMLVideoElement | null>(null)
331
+ const videoKey = ref(0)
332
+ let livePlayer: LivePlayer | null = null
333
+
334
+ async function startStream(cameraId: string) {
335
+ // Stop previous player
336
+ if (livePlayer) {
337
+ livePlayer.stop()
338
+ livePlayer = null
339
+ }
340
+
341
+ // CRITICAL: Increment key to force Vue to create a new <video> element
342
+ videoKey.value++
343
+ await nextTick() // Wait for new element to be in DOM
344
+
345
+ livePlayer = new LivePlayer()
346
+ await livePlayer.start({
347
+ videoElement: videoRef.value!,
348
+ cameraId,
349
+ baseUrl: authStore.baseUrl ?? '',
350
+ jwt: authStore.token ?? ''
351
+ })
352
+ }
353
+ </script>
354
+
355
+ <template>
356
+ <!-- :key forces a fresh DOM element when videoKey changes -->
357
+ <video :key="videoKey" ref="videoRef" autoplay muted playsinline />
358
+ </template>
359
+ ```
360
+
361
+ **Why this is needed:** After `livePlayer.stop()`, the old `<video>` element retains stale
362
+ MediaSource/WebSocket state. Creating a new LivePlayer on the same element fails silently —
363
+ the video appears frozen on the previous camera's last frame. The `:key` trick makes Vue
364
+ destroy and recreate the DOM element, giving the new LivePlayer a clean slate.
365
+
315
366
  ## Error Handling
316
367
 
317
368
  | Error Code | Meaning | Action |
@@ -332,3 +383,5 @@ The video element MUST be rendered in the DOM before calling `player.start()`.
332
383
  | "Video Stream is done" immediately | Config passed to LivePlayer constructor | MUST use `new LivePlayer()` with no args, then `player.start(config)` |
333
384
  | "Video element not found" | Video element not in DOM | Ensure video element is rendered (not hidden by v-if) before SDK init |
334
385
  | Black video, no errors (LivePlayer) | Video element hidden by v-if | Use CSS visibility/opacity instead of v-if for conditional video display |
386
+ | Video doesn't change on camera switch | LivePlayer doesn't release video element cleanly | Use Vue `:key` on `<video>` element, increment on each switch (see Camera Switching section) |
387
+ | Used multipartUrl for "HD video" request | multipartUrl only supports preview feeds | Use Live Video SDK (`@een/live-video-web-sdk`) for HD/main feed video |
@@ -2,8 +2,8 @@
2
2
  name: een-setup-agent
3
3
  description: |
4
4
  Use this agent when creating a new Vue 3 application with een-api-toolkit,
5
- when fixing Pinia initialization errors, when troubleshooting OAuth redirect
6
- URI issues, or when setting up Vite configuration for EEN applications.
5
+ when fixing Pinia initialization errors, or when setting up Vite configuration
6
+ for EEN applications.
7
7
  model: inherit
8
8
  color: green
9
9
  ---
@@ -26,34 +26,37 @@ assistant: "I'll use the een-setup-agent to diagnose and fix the Pinia initializ
26
26
  <Task tool call to launch een-setup-agent>
27
27
  </example>
28
28
 
29
- <example>
30
- Context: User has OAuth redirect issues.
31
- user: "My OAuth login redirects to the wrong URL"
32
- assistant: "I'll use the een-setup-agent to check your vite.config.ts and redirect URI configuration."
33
- <Task tool call to launch een-setup-agent>
34
- </example>
35
-
36
29
  ## Context Files
37
30
  Load these documentation files before starting:
38
31
  - docs/AI-CONTEXT.md (overview)
39
32
  - docs/ai-reference/AI-SETUP.md (primary reference)
40
33
 
34
+ ## Scope and Agent Delegation
35
+
36
+ This agent handles **project scaffolding only**: Vite config, main.ts initialization,
37
+ basic router structure, placeholder views, and environment variables.
38
+
39
+ **After scaffolding, delegate to specialized agents for feature implementation:**
40
+
41
+ - **een-auth-agent** — OAuth login/logout views, auth callback handling, route guards,
42
+ session restoration (`authStore.initialize()`), Playwright E2E auth tests.
43
+ Knows the EEN two-step login flow and correct Playwright selectors.
44
+ - **een-media-agent** — Live video, camera previews, recorded images, HLS playback.
45
+ Knows when to use Live Video SDK vs multipartUrl and how to handle camera switching.
46
+ - **een-devices-agent** — Camera/bridge listing, filtering, and device details.
47
+
48
+ **Do NOT implement OAuth views (Login.vue, Callback.vue, Logout.vue) or media components
49
+ yourself.** Create placeholder views, then let the caller invoke the specialized agent.
50
+
41
51
  ## Your Capabilities
42
- 1. Create new Vue 3 project structure for EEN applications
43
- 2. Configure main.ts with proper Pinia + toolkit initialization
44
- 3. Set up vite.config.ts for EEN requirements (127.0.0.1:3333)
45
- 4. Configure Vue Router with OAuth callback handling
46
- 5. Set up environment variables
47
- 6. Debug common setup errors (Pinia not active, redirect URI mismatch)
48
-
49
- ## Workflow
50
- 1. Verify prerequisites (Node 20+, Vue 3, Pinia)
51
- 2. Create or modify configuration files
52
- 3. Set up router with OAuth callback pattern
53
- 4. Verify setup by checking for common errors
54
- 5. Reference examples/vue-users/ for working patterns
55
-
56
- ## Key Configuration Points
52
+ 1. Create Vue 3 + Vite + TypeScript project structure
53
+ 2. Configure main.ts with proper Pinia + toolkit initialization order
54
+ 3. Set up vite.config.ts (host: 127.0.0.1, port: 3333)
55
+ 4. Create basic router with placeholder routes for /, /login, /callback, /logout
56
+ 5. Set up .env environment variables
57
+ 6. Debug Pinia initialization errors
58
+
59
+ ## Key Configuration
57
60
 
58
61
  ### main.ts Initialization Order
59
62
  ```typescript
@@ -74,36 +77,7 @@ app.use(router)
74
77
  app.mount('#app')
75
78
  ```
76
79
 
77
- ### App.vue Session Restoration (CRITICAL)
78
- **Users will need to re-login after every page refresh unless you call `authStore.initialize()` in App.vue.**
79
-
80
- ```vue
81
- <script setup lang="ts">
82
- import { onMounted, computed } from 'vue'
83
- import { useAuthStore } from 'een-api-toolkit'
84
-
85
- const authStore = useAuthStore()
86
- const isAuthenticated = computed(() => authStore.isAuthenticated)
87
-
88
- // CRITICAL: Initialize auth store from storage on app mount
89
- // This restores the session if a valid token exists in localStorage/sessionStorage
90
- onMounted(() => {
91
- authStore.initialize()
92
- })
93
- </script>
94
- ```
95
-
96
- Without `initialize()`:
97
- - Token is saved to localStorage after login ✓
98
- - On page refresh, Pinia store starts with empty state
99
- - `isAuthenticated` returns false → user must login again
100
-
101
- With `initialize()`:
102
- - Token is loaded from localStorage on app mount
103
- - `isAuthenticated` returns true → session is restored
104
- - User can continue without re-logging in
105
-
106
- ### vite.config.ts for EEN OAuth
80
+ ### vite.config.ts
107
81
  ```typescript
108
82
  import { defineConfig } from 'vite'
109
83
  import vue from '@vitejs/plugin-vue'
@@ -111,48 +85,25 @@ import vue from '@vitejs/plugin-vue'
111
85
  export default defineConfig({
112
86
  plugins: [vue()],
113
87
  server: {
114
- // IMPORTANT: Must use 127.0.0.1:3333 for EEN OAuth callback
115
- // The EEN Identity Provider only permits this specific redirect URI
116
- host: '127.0.0.1',
88
+ host: '127.0.0.1', // REQUIRED: Must match EEN OAuth redirect URI
117
89
  port: 3333
118
90
  }
119
91
  })
120
92
  ```
121
93
 
122
- ### Router with OAuth Callback
123
- ```typescript
124
- import { createRouter, createWebHistory } from 'vue-router'
125
-
126
- const routes = [
127
- { path: '/', component: () => import('@/views/Home.vue') },
128
- { path: '/login', component: () => import('@/views/Login.vue') },
129
- { path: '/callback', component: () => import('@/views/Callback.vue') },
130
- { path: '/logout', component: () => import('@/views/Logout.vue') }
131
- ]
132
-
133
- const router = createRouter({
134
- history: createWebHistory(),
135
- routes
136
- })
137
-
138
- export default router
139
- ```
94
+ ### Router Skeleton
95
+ Create routes for /, /login, /callback, /logout. Auth guards and OAuth callback
96
+ handling should be implemented by the **een-auth-agent**.
140
97
 
141
98
  ## Constraints
142
99
  - Always use 127.0.0.1, never localhost
143
100
  - Always use port 3333
144
101
  - Pinia must be installed before initEenToolkit()
145
- - Never add trailing slashes to redirect URIs
146
- - Ensure VITE_PROXY_URL is set in .env file
147
- - **Always call `authStore.initialize()` in App.vue onMounted** for session persistence
102
+ - Ensure VITE_PROXY_URL and VITE_EEN_CLIENT_ID are set in .env
148
103
 
149
- ## Common Errors and Solutions
104
+ ## Common Errors
150
105
 
151
106
  | Error | Cause | Solution |
152
107
  |-------|-------|----------|
153
- | "Pinia not active" | initEenToolkit() called before pinia.use() | Reorder initialization in main.ts |
154
- | "redirect_uri mismatch" | Wrong host/port | Use 127.0.0.1:3333 in vite.config.ts |
155
- | "Invalid redirect_uri" | Trailing slash | Remove trailing slash from redirect URI |
156
- | "CORS error" | Proxy not running | Start the OAuth proxy server |
157
- | Session lost on refresh | Missing initialize() call | Add `authStore.initialize()` in App.vue onMounted |
158
- | Must login after refresh | Missing initialize() call | Add `authStore.initialize()` in App.vue onMounted |
108
+ | "Pinia not active" | initEenToolkit() called before app.use(pinia) | Reorder initialization in main.ts |
109
+ | Auth/OAuth errors | See **een-auth-agent** | Delegate to een-auth-agent |
package/CHANGELOG.md CHANGED
@@ -2,37 +2,10 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
- ## [0.3.79] - 2026-02-14
5
+ ## [0.3.81] - 2026-02-14
6
6
 
7
7
  ### Release Summary
8
8
 
9
- #### PR #108: Release v0.3.76: Hostname validation security hardening
10
- ## Summary
11
- - **Security Fix**: Validate hostname against EEN domain allowlist to prevent token exfiltration via malicious base URL injection
12
- - **Hardening**: Fail-secure on tampered storage - clears all auth data when poisoned hostname/port detected
13
- - **Validation**: Port validation (1-65535 range), protocol bypass prevention, subdomain spoofing protection
14
- - **Tests**: Comprehensive hostname validation test suite for auth store (46 new tests)
15
- - **Robustness**: Added `isAllowedEenHostname` utility with falsy guard, console.warn for rejected hostnames, `@internal` JSDoc tag on `ALLOWED_DOMAINS`
16
-
17
- ## Commits
18
- - fix: Validate hostname against EEN domain allowlist to prevent token exfiltration
19
- - test: Add hostname validation tests for auth store security fix
20
- - docs: Add @internal JSDoc tag to ALLOWED_DOMAINS constant
21
- - fix: Use console.warn for rejected hostname validation messages
22
- - fix: Add falsy guard to isAllowedEenHostname for robustness
23
- - fix: harden hostname/port validation and fail-secure on tampered storage
24
-
25
- ## Test Results
26
- - **Lint**: Passed (1 warning - pre-existing)
27
- - **Unit Tests**: 639 passed (23 test files)
28
- - **Build**: Successful (ESM + CJS)
29
- - **E2E Tests**: All 11 example apps passed
30
-
31
- ## Version
32
- `0.3.76`
33
-
34
- 🤖 Generated with [Claude Code](https://claude.com/claude-code)
35
-
36
9
  #### PR #110: fix: Security hardening - hostname validation and CI/CD pipeline
37
10
  ## Summary
38
11
 
@@ -69,18 +42,51 @@ Release v0.3.79 - Security hardening fixes for three vulnerabilities identified
69
42
 
70
43
  🤖 Generated with [Claude Code](https://claude.com/claude-code)
71
44
 
45
+ #### PR #112: fix: revert broken head_branch check and update agents/skill
46
+ ## Summary
47
+
48
+ Release v0.3.80 with two key changes:
49
+
50
+ 1. **Fix broken publish workflow** — The `head_branch == 'production'` check added in v0.3.79 caused all automated npm publish runs to be skipped. GitHub's `workflow_run.head_branch` refers to the PR source branch (e.g. `develop`), not the target. The upstream `test-release.yml` already restricts to PRs merged to production.
51
+
52
+ 2. **Add confidential data scan to PR-and-check skill** — New step that scans changed `.md` files for secrets, credentials, or confidential data before PR creation. Critical safeguard for a public npm package.
53
+
54
+ 3. **Agent documentation improvements** — Updated Playwright selectors (getByPlaceholder), SSE API patterns (nested deliveryConfig/filters), LivePlayer camera switching (:key trick), and setup agent scope (delegation to specialized agents).
55
+
56
+ ## Commits
57
+
58
+ - `1bb7297` fix: revert broken head_branch check in npm-publish workflow
59
+ - `6afd34a` chore: add confidential data scan to PR-and-check skill and update agents
60
+ - `c5ff7eb` Merge pull request #111
61
+
62
+ ## Test Results
63
+
64
+ - **Lint**: 0 errors (1 pre-existing warning)
65
+ - **Unit tests**: 644 passed
66
+ - **Build**: Success
67
+ - **E2E tests**: 3/11 passed before flaky auth timeout in vue-cameras (camera-settings.spec.ts:328, unrelated to changes)
68
+ - **Security review**: No vulnerabilities found
69
+ - **Confidential data scan**: No secrets in 224 .md files
70
+
71
+ ## Version
72
+
73
+ `0.3.80`
74
+
75
+ 🤖 Generated with [Claude Code](https://claude.com/claude-code)
76
+
72
77
 
73
78
  ### Detailed Changes
74
79
 
75
80
  #### Bug Fixes
76
- - fix: harden hostname validation and CI/CD security
81
+ - fix: use Vue ref() pattern in een-events-agent SSE example
82
+ - fix: revert broken head_branch check in npm-publish workflow
77
83
 
78
84
  #### Other Changes
79
- - Update src/utils/hostname.ts
85
+ - chore: add confidential data scan to PR-and-check skill and update agents
80
86
 
81
87
  ### Links
82
88
  - [npm package](https://www.npmjs.com/package/een-api-toolkit)
83
- - [Full Changelog](https://github.com/klaushofrichter/een-api-toolkit/compare/v0.3.78...v0.3.79)
89
+ - [Full Changelog](https://github.com/klaushofrichter/een-api-toolkit/compare/v0.3.79...v0.3.81)
84
90
 
85
91
  ---
86
- *Released: 2026-02-14 07:56:49 CST*
92
+ *Released: 2026-02-14 10:39:08 CST*
@@ -1,6 +1,6 @@
1
1
  # EEN API Toolkit - AI Reference
2
2
 
3
- > **Version:** 0.3.79
3
+ > **Version:** 0.3.81
4
4
  >
5
5
  > This documentation is optimized for AI assistants. It provides focused, domain-specific
6
6
  > references to help you understand and use the een-api-toolkit efficiently.
@@ -1,6 +1,6 @@
1
1
  # Authentication - EEN API Toolkit
2
2
 
3
- > **Version:** 0.3.79
3
+ > **Version:** 0.3.81
4
4
  >
5
5
  > OAuth flow implementation, token management, and session handling.
6
6
  > Load this document when implementing login, logout, or auth guards.
@@ -1,6 +1,6 @@
1
1
  # Automations API - EEN API Toolkit
2
2
 
3
- > **Version:** 0.3.79
3
+ > **Version:** 0.3.81
4
4
  >
5
5
  > Complete reference for automation rules and alert actions.
6
6
  > Load this document when working with automated alert workflows.
@@ -1,6 +1,6 @@
1
1
  # Cameras & Bridges API - EEN API Toolkit
2
2
 
3
- > **Version:** 0.3.79
3
+ > **Version:** 0.3.81
4
4
  >
5
5
  > Complete reference for camera and bridge management.
6
6
  > Load this document when working with devices.
@@ -1,6 +1,6 @@
1
1
  # Event Type to Data Schemas Mapping - EEN API Toolkit
2
2
 
3
- > **Version:** 0.3.79
3
+ > **Version:** 0.3.81
4
4
  >
5
5
  > Complete reference for event type to data schema mappings.
6
6
  > Load this document when building dynamic event queries with the `include` parameter.
@@ -1,6 +1,6 @@
1
1
  # Events, Alerts & Real-Time Streaming - EEN API Toolkit
2
2
 
3
- > **Version:** 0.3.79
3
+ > **Version:** 0.3.81
4
4
  >
5
5
  > Complete reference for events, alerts, metrics, and SSE subscriptions.
6
6
  > Load this document when implementing event-driven features.
@@ -1,6 +1,6 @@
1
1
  # Layouts API - EEN API Toolkit
2
2
 
3
- > **Version:** 0.3.79
3
+ > **Version:** 0.3.81
4
4
  >
5
5
  > Complete reference for layout management (camera grouping).
6
6
  > Load this document when working with layouts.
@@ -1,6 +1,6 @@
1
1
  # Jobs, Exports, Files & Downloads - EEN API Toolkit
2
2
 
3
- > **Version:** 0.3.79
3
+ > **Version:** 0.3.81
4
4
  >
5
5
  > Complete reference for async jobs, video exports, and file management.
6
6
  > Load this document when implementing export workflows or file downloads.
@@ -1,6 +1,6 @@
1
1
  # Media & Live Video - EEN API Toolkit
2
2
 
3
- > **Version:** 0.3.79
3
+ > **Version:** 0.3.81
4
4
  >
5
5
  > Complete reference for media retrieval, live streaming, and video playback.
6
6
  > Load this document when implementing video features.
@@ -1,6 +1,6 @@
1
1
  # Vue 3 Application Setup - EEN API Toolkit
2
2
 
3
- > **Version:** 0.3.79
3
+ > **Version:** 0.3.81
4
4
  >
5
5
  > Complete guide for setting up a Vue 3 application with the een-api-toolkit.
6
6
  > Load this document when creating a new project or troubleshooting setup issues.
@@ -1,6 +1,6 @@
1
1
  # Users API - EEN API Toolkit
2
2
 
3
- > **Version:** 0.3.79
3
+ > **Version:** 0.3.81
4
4
  >
5
5
  > Complete reference for user management.
6
6
  > Load this document when working with user data.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "een-api-toolkit",
3
- "version": "0.3.79",
3
+ "version": "0.3.81",
4
4
  "description": "EEN Video platform API v3.0 library for Vue 3",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",