een-api-toolkit 0.3.7 → 0.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,57 +2,26 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
- ## [0.3.7] - 2026-01-06
5
+ ## [0.3.8] - 2026-01-09
6
6
 
7
7
  ### Release Summary
8
8
 
9
- #### PR #46: feat: Add configurable storage strategy for token persistence
10
- ## Summary
11
-
12
- - Add `storageStrategy` option to `initEenToolkit()` allowing apps to choose how authentication tokens are persisted
13
- - Three storage options with different security/UX tradeoffs:
14
- - `localStorage` (default): Persists across sessions, backwards compatible
15
- - `sessionStorage`: Per-tab isolation, cleared on tab close
16
- - `memory`: Maximum security, tokens never written to disk
17
- - Addresses security finding about JWT tokens in localStorage being vulnerable to XSS
18
-
19
- ## Changes
20
-
21
- - Add `StorageAdapter` abstraction with localStorage/sessionStorage/memory implementations
22
- - Add `storageStrategy` config option (default: `localStorage` for backwards compatibility)
23
- - Export `StorageStrategy` type and `getStorageStrategy()` function
24
- - Update auth store to use configurable storage adapter
25
- - Add 24 unit tests for storage functionality
26
- - Document security vs UX tradeoffs in USER-GUIDE.md
27
-
28
- ## Additional fixes
29
-
30
- - Update EEN API docs link from `/reference/listusers` to `/reference/getusers`
31
- - Replace "~1 hour" token validity references with accurate configurable range (15min-7days)
32
-
33
- ## Test plan
34
-
35
- - [x] All 188 unit tests pass
36
- - [x] Build succeeds
37
- - [x] Lint passes
38
- - [ ] Verify backwards compatibility (existing apps work without changes)
39
- - [ ] Test each storage strategy manually
40
-
41
- 🤖 Generated with [Claude Code](https://claude.com/claude-code)
42
-
9
+ No PR descriptions available for this release.
43
10
 
44
11
  ### Detailed Changes
45
12
 
46
13
  #### Features
47
- - feat: Add configurable storage strategy for token persistence
14
+ - feat: Enhance sync-secrets.sh to sync .env to example apps
48
15
 
49
16
  #### Bug Fixes
50
- - fix: Propagate storage errors to auth/store for proper logging
51
- - fix: Address PR review feedback and demonstrate storage strategies
17
+ - fix: Update AI-CONTEXT generator with missing documentation
18
+
19
+ #### Other Changes
20
+ - docs: Optimize AI-CONTEXT.md for LLM consumption
52
21
 
53
22
  ### Links
54
23
  - [npm package](https://www.npmjs.com/package/een-api-toolkit)
55
- - [Full Changelog](https://github.com/klaushofrichter/een-api-toolkit/compare/v0.3.4...v0.3.7)
24
+ - [Full Changelog](https://github.com/klaushofrichter/een-api-toolkit/compare/v0.3.7...v0.3.8)
56
25
 
57
26
  ---
58
- *Released: 2026-01-06 12:25:59 CST*
27
+ *Released: 2026-01-09 17:31:59 CST*
package/README.md CHANGED
@@ -241,6 +241,46 @@ npm run test:e2e:ui
241
241
 
242
242
  > **Note:** All development and testing has been done on macOS. The `lsof` command used in scripts may behave differently on other platforms.
243
243
 
244
+ ## Scripts
245
+
246
+ Utility scripts are located in the `scripts/` directory:
247
+
248
+ | Script | Purpose |
249
+ |--------|---------|
250
+ | `sync-secrets.sh` | Sync secrets to GitHub and example apps |
251
+ | `restart-proxy.sh` | Start/restart the local OAuth proxy |
252
+ | `cleanup-auth.sh` | Revoke test tokens and clear auth cache |
253
+
254
+ ### Syncing Secrets
255
+
256
+ The `sync-secrets.sh` script manages secrets from a single source (root `.env` file):
257
+
258
+ ```bash
259
+ # Preview what will be synced (no changes made)
260
+ ./scripts/sync-secrets.sh --dry-run
261
+
262
+ # Sync secrets to GitHub and example applications
263
+ ./scripts/sync-secrets.sh
264
+ ```
265
+
266
+ **What it does:**
267
+
268
+ 1. **GitHub Repository Secrets** - Syncs these variables for CI/CD:
269
+ - `ANTHROPIC_API_KEY`, `CLIENT_SECRET`, `NPM_TOKEN`, `SLACK_WEBHOOK`
270
+ - `TEST_USER`, `TEST_PASSWORD`, `VITE_EEN_CLIENT_ID`, `VITE_PROXY_URL`
271
+
272
+ 2. **Example Applications** - Copies VITE_* variables to `examples/*/.env`:
273
+ - `VITE_PROXY_URL`, `VITE_EEN_CLIENT_ID`, `VITE_DEBUG`
274
+ - `VITE_REDIRECT_URI` (hardcoded to `http://127.0.0.1:3333`)
275
+
276
+ **Setup:**
277
+
278
+ 1. Copy `.env.example` to `.env` in the project root
279
+ 2. Fill in your actual values
280
+ 3. Run `./scripts/sync-secrets.sh` to distribute secrets
281
+
282
+ > **Note:** Example `.env` files are gitignored. Run `sync-secrets.sh` after cloning to set up local development.
283
+
244
284
  ## External Resources
245
285
 
246
286
  - [EEN Developer Portal](https://developer.eagleeyenetworks.com/)
@@ -1,6 +1,6 @@
1
1
  # EEN API Toolkit - AI Reference
2
2
 
3
- > **Version:** 0.3.7
3
+ > **Version:** 0.3.8
4
4
  >
5
5
  > This file is optimized for AI assistants. It contains all API signatures,
6
6
  > types, and usage patterns in a single, parseable document.
@@ -103,10 +103,98 @@ app.mount('#app') // ✅ Last - mount app
103
103
 
104
104
  **Solution:**
105
105
  - Use `127.0.0.1` not `localhost`
106
- - Use port `3333`
107
- - No trailing slash, no path
106
+ - Use port `3333` exactly
107
+ - No trailing slash (not `http://127.0.0.1:3333/`)
108
+ - No path (not `http://127.0.0.1:3333/callback`)
108
109
  - Register this exact URI at [EEN Developer Portal](https://developer.eagleeyenetworks.com/page/my-application)
109
110
 
111
+ **Vite config:**
112
+ ```typescript
113
+ // vite.config.ts
114
+ server: { host: '127.0.0.1', port: 3333, strictPort: true }
115
+ ```
116
+
117
+ **Router pattern:** Handle OAuth callback on root path, then redirect internally:
118
+ ```typescript
119
+ // router/index.ts - root path must catch OAuth params
120
+ {
121
+ path: '/',
122
+ beforeEnter: (to, _from, next) => {
123
+ if (to.query.code && to.query.state) {
124
+ next({ name: 'callback', query: to.query })
125
+ } else {
126
+ next()
127
+ }
128
+ }
129
+ }
130
+ ```
131
+
132
+ ### Common Pitfalls - Preview Images
133
+
134
+ Displaying live preview images requires careful attention to authentication. Here are common mistakes:
135
+
136
+ #### DON'T: Construct API URLs directly for `<img>` tags
137
+
138
+ ```typescript
139
+ // WRONG - browsers cannot send Authorization headers with <img src>
140
+ const url = `${authStore.baseUrl}/api/v3.0/media/liveImage.jpeg?deviceId=${cameraId}`
141
+ imgElement.src = url // Results in 401 Unauthorized
142
+ ```
143
+
144
+ **Why it fails:** The `<img>` element makes a simple GET request without custom headers. The JWT token cannot be sent, so the request is unauthorized.
145
+
146
+ #### DON'T: Modify multipartUrl by adding query parameters
147
+
148
+ ```typescript
149
+ // WRONG - adding parameters breaks the pre-signed URL
150
+ const feedUrl = feed.multipartUrl
151
+ imgElement.src = `${feedUrl}?timestamp=${Date.now()}` // Results in 400 Bad Request
152
+ imgElement.src = `${feedUrl}&cache=${Date.now()}` // Also fails
153
+ ```
154
+
155
+ **Why it fails:** The `multipartUrl` is a complete, pre-authenticated URL. Any modification (including cache-busting parameters) invalidates it.
156
+
157
+ #### DO: Use `getLiveImage()` for preview thumbnails
158
+
159
+ ```typescript
160
+ // CORRECT - returns base64 data URL that works directly in <img src>
161
+ import { getLiveImage } from 'een-api-toolkit'
162
+
163
+ const { data, error } = await getLiveImage({ deviceId: cameraId })
164
+ if (data?.imageData) {
165
+ imgElement.src = data.imageData // "data:image/jpeg;base64,..."
166
+ }
167
+ ```
168
+
169
+ **Why it works:** The toolkit handles authentication internally and returns a base64-encoded data URL that can be used directly without browser restrictions.
170
+
171
+ ### Choosing the Right Preview Method
172
+
173
+ | Use Case | Method | Notes |
174
+ |----------|--------|-------|
175
+ | Grid of camera thumbnails | `getLiveImage()` | Best for multiple cameras, handles auth internally |
176
+ | Periodic refresh (e.g., every 3s) | `getLiveImage()` | Call repeatedly in a timer |
177
+ | Single continuous MJPEG stream | `multipartUrl` | Requires `initMediaSession()` first, use URL unmodified |
178
+ | One-time snapshot | `getLiveImage()` | Simple and self-contained |
179
+ | Full-quality live video | Live Video SDK | For modal/fullscreen video playback |
180
+
181
+ **Quick Reference:**
182
+
183
+ ```typescript
184
+ // For thumbnails and grids - USE THIS
185
+ const { data } = await getLiveImage({ deviceId })
186
+ img.src = data.imageData
187
+
188
+ // For continuous MJPEG stream (single camera, unmodified URL only)
189
+ await initMediaSession()
190
+ const { data: feeds } = await listFeeds({ deviceId, include: ['multipartUrl'] })
191
+ img.src = feeds.results.find(f => f.type === 'preview')?.multipartUrl
192
+
193
+ // For HD live video - use @een/live-video-web-sdk
194
+ const player = new LivePlayer()
195
+ player.start({ videoElement, cameraId, baseUrl, jwt })
196
+ ```
197
+
110
198
  ---
111
199
 
112
200
  ## Quick Reference
@@ -153,6 +241,7 @@ app.mount('#app') // ✅ Last - mount app
153
241
  | Function | Purpose | Returns |
154
242
  |----------|---------|---------|
155
243
  | `listMedia(params)` | List media intervals for a device | `Result<PaginatedResult<MediaInterval>>` |
244
+ | `listFeeds(params)` | List available feeds for a device | `Result<ListFeedsResult>` |
156
245
  | `getLiveImage(params)` | Get live preview image from camera | `Result<LiveImageResult>` |
157
246
  | `getRecordedImage(params)` | Get recorded image from history | `Result<RecordedImageResult>` |
158
247
  | `getMediaSession()` | Get media session URL for cookies | `Result<MediaSessionResponse>` |
@@ -160,72 +249,6 @@ app.mount('#app') // ✅ Last - mount app
160
249
 
161
250
  ---
162
251
 
163
- ## Critical Requirements
164
-
165
- ### OAuth Redirect URI (IMPORTANT)
166
-
167
- The EEN Identity Provider performs an **exact string match** on the redirect URI. Applications MUST follow these rules:
168
-
169
- | Requirement | Correct | Incorrect |
170
- |-------------|---------|-----------|
171
- | Host | `127.0.0.1` | `localhost` |
172
- | Path | None (root path only) | `/callback` |
173
- | Trailing slash | No | `http://127.0.0.1:3333/` |
174
-
175
- **The only valid redirect URI is: `http://127.0.0.1:3333`**
176
-
177
- **Configure at:** [EEN Developer Portal - My Application](https://developer.eagleeyenetworks.com/page/my-application)
178
-
179
- ### Application Requirements
180
-
181
- 1. **Handle OAuth callbacks on the root path (`/`)** - not `/callback`
182
- 2. **Run dev server on `127.0.0.1`** - not `localhost`
183
- 3. **Register exactly `http://127.0.0.1:3333` with EEN at the Developer Portal**
184
-
185
- ### Router Pattern for OAuth Callbacks
186
-
187
- The root path must detect OAuth params and handle the callback:
188
-
189
- ```typescript
190
- // router/index.ts
191
- {
192
- path: '/',
193
- name: 'home',
194
- component: Home,
195
- beforeEnter: (to, _from, next) => {
196
- // If URL has OAuth params, redirect to callback handler
197
- if (to.query.code && to.query.state) {
198
- next({ name: 'callback', query: to.query })
199
- } else {
200
- next()
201
- }
202
- }
203
- }
204
- ```
205
-
206
- ### Vite Dev Server Configuration
207
-
208
- ```typescript
209
- // vite.config.ts
210
- export default defineConfig({
211
- server: {
212
- host: '127.0.0.1', // MUST use 127.0.0.1, not localhost
213
- port: 3333,
214
- strictPort: true
215
- }
216
- })
217
- ```
218
-
219
- ### Common OAuth Errors
220
-
221
- | Error | Cause | Solution |
222
- |-------|-------|----------|
223
- | "Redirect URI mismatch" | URI doesn't match exactly | Use `http://127.0.0.1:3333` (no path, no trailing slash) |
224
- | Redirected back to login | Router guard blocks callback | Allow OAuth params through on root path |
225
- | Callback not processed | Wrong path or host | Handle callback on `/`, use `127.0.0.1` |
226
-
227
- ---
228
-
229
252
  ## Core Types
230
253
 
231
254
  ### Result<T>
@@ -245,16 +268,17 @@ interface EenError {
245
268
  }
246
269
 
247
270
  type ErrorCode =
248
- | 'AUTH_REQUIRED' // No valid token - redirect to login
249
- | 'AUTH_FAILED' // Authentication failed
250
- | 'TOKEN_EXPIRED' // Token expired - will auto-refresh
251
- | 'API_ERROR' // EEN API returned an error
252
- | 'NETWORK_ERROR' // Network request failed
253
- | 'VALIDATION_ERROR' // Invalid parameters
254
- | 'NOT_FOUND' // Resource not found (404)
255
- | 'FORBIDDEN' // Access denied (403)
256
- | 'RATE_LIMITED' // Too many requests (429)
257
- | 'UNKNOWN_ERROR' // Unexpected error
271
+ | 'AUTH_REQUIRED' // No valid token - redirect to login
272
+ | 'AUTH_FAILED' // Authentication failed
273
+ | 'TOKEN_EXPIRED' // Token expired - will auto-refresh
274
+ | 'API_ERROR' // EEN API returned an error
275
+ | 'NETWORK_ERROR' // Network request failed
276
+ | 'VALIDATION_ERROR' // Invalid parameters
277
+ | 'NOT_FOUND' // Resource not found (404)
278
+ | 'FORBIDDEN' // Access denied (403)
279
+ | 'RATE_LIMITED' // Too many requests (429)
280
+ | 'SERVICE_UNAVAILABLE' // Service unavailable (503)
281
+ | 'UNKNOWN_ERROR' // Unexpected error
258
282
  ```
259
283
 
260
284
  ### Pagination Types
@@ -276,11 +300,14 @@ interface PaginatedResult<T> {
276
300
  ### Configuration Type
277
301
 
278
302
  ```typescript
303
+ type StorageStrategy = 'localStorage' | 'sessionStorage' | 'memory'
304
+
279
305
  interface EenToolkitConfig {
280
- proxyUrl?: string // OAuth proxy URL (required for API calls)
281
- clientId?: string // EEN OAuth client ID
282
- redirectUri?: string // OAuth redirect URI (default: http://127.0.0.1:3333)
283
- debug?: boolean // Enable debug logging
306
+ proxyUrl?: string // OAuth proxy URL (required for API calls)
307
+ clientId?: string // EEN OAuth client ID
308
+ redirectUri?: string // OAuth redirect URI (default: http://127.0.0.1:3333)
309
+ storageStrategy?: StorageStrategy // Token storage: 'localStorage' (default), 'sessionStorage', or 'memory'
310
+ debug?: boolean // Enable debug logging
284
311
  }
285
312
  ```
286
313
 
@@ -582,6 +609,44 @@ interface MediaSessionResult {
582
609
  }
583
610
  ```
584
611
 
612
+ ### Feed Types
613
+
614
+ ```typescript
615
+ type FeedStreamType = 'main' | 'preview' | 'talkdown'
616
+ type FeedMediaType = 'video' | 'audio' | 'image' | 'halfDuplex' | 'fullDuplex'
617
+ type FeedIncludeOption = 'flvUrl' | 'rtspUrl' | 'rtspsUrl' | 'localRtspUrl' | 'hlsUrl' | 'multipartUrl' | 'webRtcUrl' | 'audioPushHttpsUrl'
618
+
619
+ interface Feed {
620
+ id: string // Feed identifier (typically deviceId-type)
621
+ type: FeedStreamType // Stream type
622
+ deviceId: string // Device generating this feed
623
+ mediaType: FeedMediaType // Media type
624
+ flvUrl?: string | null // Flash Video URL (if requested)
625
+ rtspUrl?: string | null // RTSP URL (if requested)
626
+ rtspsUrl?: string | null // RTSP over TLS (if requested)
627
+ localRtspUrl?: string | null // Local RTSP to bridge (if requested)
628
+ hlsUrl?: string | null // HLS URL (if requested)
629
+ multipartUrl?: string | null // Multipart URL for raw frames (if requested)
630
+ webRtcUrl?: string | null // WebRTC URL (if requested)
631
+ audioPushHttpsUrl?: string | null // Audio push for speakers (if requested)
632
+ }
633
+
634
+ interface ListFeedsParams {
635
+ deviceId?: string // Filter by single device ID
636
+ deviceId__in?: string[] // Filter by multiple device IDs
637
+ type?: FeedStreamType // Filter by stream type
638
+ include?: FeedIncludeOption[] // URL fields to include in response
639
+ pageSize?: number
640
+ pageToken?: string
641
+ }
642
+
643
+ interface ListFeedsResult {
644
+ results: Feed[]
645
+ nextPageToken?: string
646
+ totalSize?: number
647
+ }
648
+ ```
649
+
585
650
  ---
586
651
 
587
652
  ## API Reference
@@ -1354,100 +1419,6 @@ console.log(data.email) // Safe
1354
1419
 
1355
1420
  ---
1356
1421
 
1357
- ## Additional Setup Details
1358
-
1359
- ### OAuth Callback Route
1360
-
1361
- The `beforeEnter` guard shown in the "Critical Requirements" section redirects OAuth responses to a named route called 'callback'. Here is how to set up that route and its component:
1362
-
1363
- ```typescript
1364
- // router/index.ts
1365
- {
1366
- path: '/callback',
1367
- name: 'callback',
1368
- component: () => import('./views/Callback.vue')
1369
- }
1370
- ```
1371
-
1372
- ```vue
1373
- <!-- views/Callback.vue -->
1374
- <script setup lang="ts">
1375
- import { onMounted } from 'vue'
1376
- import { useRouter } from 'vue-router'
1377
- import { handleAuthCallback } from 'een-api-toolkit'
1378
-
1379
- const router = useRouter()
1380
-
1381
- onMounted(async () => {
1382
- const url = new URL(window.location.href)
1383
- const code = url.searchParams.get('code')
1384
- const state = url.searchParams.get('state')
1385
-
1386
- if (!code || !state) {
1387
- router.push('/login?error=missing_params')
1388
- return
1389
- }
1390
-
1391
- const { error } = await handleAuthCallback(code, state)
1392
-
1393
- if (error) {
1394
- router.push(`/login?error=${error.code}`)
1395
- return
1396
- }
1397
-
1398
- router.push('/dashboard')
1399
- })
1400
- </script>
1401
-
1402
- <template>
1403
- <div>Authenticating...</div>
1404
- </template>
1405
- ```
1406
-
1407
- ### Login Component
1408
-
1409
- ```vue
1410
- <!-- views/Login.vue -->
1411
- <script setup>
1412
- import { getAuthUrl } from 'een-api-toolkit'
1413
-
1414
- function login() {
1415
- window.location.href = getAuthUrl()
1416
- }
1417
- </script>
1418
-
1419
- <template>
1420
- <div>
1421
- <h1>Login</h1>
1422
- <button @click="login">Sign in with Eagle Eye Networks</button>
1423
- </div>
1424
- </template>
1425
- ```
1426
-
1427
- ### Logout Component
1428
-
1429
- ```vue
1430
- <!-- In a component, e.g., App.vue or a NavBar component -->
1431
- <script setup lang="ts">
1432
- import { revokeToken } from 'een-api-toolkit'
1433
- import { useRouter } from 'vue-router'
1434
-
1435
- const router = useRouter()
1436
-
1437
- async function logout() {
1438
- await revokeToken()
1439
- // Redirect to login page after token is revoked
1440
- router.push('/login')
1441
- }
1442
- </script>
1443
-
1444
- <template>
1445
- <button @click="logout">Logout</button>
1446
- </template>
1447
- ```
1448
-
1449
- ---
1450
-
1451
1422
  ## External Resources
1452
1423
 
1453
1424
  - [Eagle Eye Networks Developer Portal](https://developer.eagleeyenetworks.com)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "een-api-toolkit",
3
- "version": "0.3.7",
3
+ "version": "0.3.8",
4
4
  "description": "EEN Video platform API v3.0 library for Vue 3",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",