een-api-toolkit 0.3.47 → 0.3.49

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 (42) hide show
  1. package/.claude/agents/een-jobs-agent.md +676 -0
  2. package/CHANGELOG.md +7 -8
  3. package/dist/index.cjs +3 -3
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.ts +1172 -28
  6. package/dist/index.js +796 -333
  7. package/dist/index.js.map +1 -1
  8. package/docs/AI-CONTEXT.md +22 -1
  9. package/docs/ai-reference/AI-AUTH.md +1 -1
  10. package/docs/ai-reference/AI-AUTOMATIONS.md +1 -1
  11. package/docs/ai-reference/AI-DEVICES.md +1 -1
  12. package/docs/ai-reference/AI-EVENTS.md +1 -1
  13. package/docs/ai-reference/AI-GROUPING.md +1 -1
  14. package/docs/ai-reference/AI-JOBS.md +1084 -0
  15. package/docs/ai-reference/AI-MEDIA.md +1 -1
  16. package/docs/ai-reference/AI-SETUP.md +1 -1
  17. package/docs/ai-reference/AI-USERS.md +1 -1
  18. package/examples/vue-jobs/.env.example +11 -0
  19. package/examples/vue-jobs/README.md +245 -0
  20. package/examples/vue-jobs/e2e/app.spec.ts +79 -0
  21. package/examples/vue-jobs/e2e/auth.spec.ts +382 -0
  22. package/examples/vue-jobs/e2e/delete-features.spec.ts +564 -0
  23. package/examples/vue-jobs/e2e/timelapse.spec.ts +361 -0
  24. package/examples/vue-jobs/index.html +13 -0
  25. package/examples/vue-jobs/package-lock.json +1722 -0
  26. package/examples/vue-jobs/package.json +28 -0
  27. package/examples/vue-jobs/playwright.config.ts +47 -0
  28. package/examples/vue-jobs/src/App.vue +154 -0
  29. package/examples/vue-jobs/src/main.ts +25 -0
  30. package/examples/vue-jobs/src/router/index.ts +82 -0
  31. package/examples/vue-jobs/src/views/Callback.vue +76 -0
  32. package/examples/vue-jobs/src/views/CreateExport.vue +284 -0
  33. package/examples/vue-jobs/src/views/Files.vue +424 -0
  34. package/examples/vue-jobs/src/views/Home.vue +195 -0
  35. package/examples/vue-jobs/src/views/JobDetail.vue +392 -0
  36. package/examples/vue-jobs/src/views/Jobs.vue +297 -0
  37. package/examples/vue-jobs/src/views/Login.vue +33 -0
  38. package/examples/vue-jobs/src/views/Logout.vue +59 -0
  39. package/examples/vue-jobs/src/vite-env.d.ts +1 -0
  40. package/examples/vue-jobs/tsconfig.json +25 -0
  41. package/examples/vue-jobs/vite.config.ts +12 -0
  42. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  # Media & Live Video - EEN API Toolkit
2
2
 
3
- > **Version:** 0.3.47
3
+ > **Version:** 0.3.49
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.47
3
+ > **Version:** 0.3.49
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.47
3
+ > **Version:** 0.3.49
4
4
  >
5
5
  > Complete reference for user management.
6
6
  > Load this document when working with user data.
@@ -0,0 +1,11 @@
1
+ # OAuth Proxy URL (required)
2
+ VITE_PROXY_URL=http://localhost:8787
3
+
4
+ # EEN OAuth Client ID (required)
5
+ VITE_EEN_CLIENT_ID=your-client-id
6
+
7
+ # OAuth Redirect URI (must match exactly)
8
+ VITE_REDIRECT_URI=http://127.0.0.1:3333
9
+
10
+ # Enable debug logging
11
+ VITE_DEBUG=true
@@ -0,0 +1,245 @@
1
+ # EEN API Toolkit - Vue Jobs Example
2
+
3
+ A complete example showing how to use the Jobs, Files, and Exports APIs from the een-api-toolkit in a Vue 3 application.
4
+
5
+ ## Storage Strategy: Memory
6
+
7
+ This example uses the `memory` storage strategy for maximum security. This means:
8
+
9
+ - **Tokens are never written to disk** - immune to localStorage/sessionStorage XSS attacks
10
+ - **Page refresh requires re-authentication** - tokens exist only in memory
11
+ - **Each tab is independent** - opening a new tab requires separate login
12
+
13
+ This is the recommended strategy for high-security deployments where protecting against XSS token theft is critical.
14
+
15
+ ## Features Demonstrated
16
+
17
+ - OAuth authentication flow (login, callback, logout)
18
+ - Protected routes with navigation guards
19
+ - List jobs with state filtering (pending, started, success, failure)
20
+ - View job details with real-time progress polling
21
+ - Create video exports from cameras
22
+ - List and download files
23
+ - Error handling with Result pattern
24
+ - Reactive authentication state
25
+
26
+ ## APIs Used
27
+
28
+ - `listJobs()` - List jobs with filtering and pagination
29
+ - `getJob()` - Get single job details
30
+ - `createExportJob()` - Create video export job
31
+ - `listFiles()` - List files with pagination
32
+ - `downloadFile()` - Download file content
33
+ - `getCameras()` - Get cameras for export selection
34
+ - `useAuthStore()` - Authentication state management
35
+ - `getAuthUrl()` - Generate OAuth login URL
36
+ - `handleAuthCallback()` - Process OAuth callback
37
+ - `initEenToolkit()` - Toolkit initialization
38
+
39
+ ## Setup
40
+
41
+ ### Prerequisites
42
+
43
+ 1. **Start the OAuth proxy** (required for authentication):
44
+
45
+ The OAuth proxy is a separate project that handles token management securely.
46
+ Clone and run it from: https://github.com/klaushofrichter/een-oauth-proxy
47
+
48
+ ```bash
49
+ # In a separate terminal, from the een-oauth-proxy directory
50
+ npm install
51
+ npm run dev
52
+ ```
53
+
54
+ The proxy should be running at `http://localhost:8787`.
55
+
56
+ ### Example Setup
57
+
58
+ All commands below should be run from this example directory (`examples/vue-jobs/`):
59
+
60
+ 2. Copy the environment file:
61
+ ```bash
62
+ # From examples/vue-jobs/
63
+ cp .env.example .env
64
+ ```
65
+
66
+ 3. Edit `.env` with your EEN credentials:
67
+ ```env
68
+ VITE_EEN_CLIENT_ID=your-client-id
69
+ VITE_PROXY_URL=http://localhost:8787
70
+ # DO NOT change the redirect URI - EEN IDP only permits this URL
71
+ VITE_REDIRECT_URI=http://127.0.0.1:3333
72
+ ```
73
+
74
+ 4. Install dependencies and start:
75
+ ```bash
76
+ # From examples/vue-jobs/
77
+ npm install
78
+ npm run dev
79
+ ```
80
+
81
+ 5. Open http://127.0.0.1:3333 in your browser.
82
+
83
+ **Important:** The EEN Identity Provider only permits `http://127.0.0.1:3333` as the OAuth redirect URI. Do not use `localhost` or other ports.
84
+
85
+ **Note:** Development and testing was done on macOS. The `npm run stop` command uses `lsof`, which is not available on Windows. Windows users should manually stop any process on port 3333 or use `npx kill-port 3333` instead.
86
+
87
+ ## Project Structure
88
+
89
+ ```
90
+ src/
91
+ ├── main.ts # App entry, toolkit initialization
92
+ ├── App.vue # Root component with navigation
93
+ ├── router/
94
+ │ └── index.ts # Vue Router with auth guards
95
+ └── views/
96
+ ├── Home.vue # Home page with user profile
97
+ ├── Login.vue # OAuth login redirect
98
+ ├── Callback.vue # OAuth callback handler
99
+ ├── Jobs.vue # Job list with state filters
100
+ ├── JobDetail.vue # Single job with progress polling
101
+ ├── Files.vue # File browser with download
102
+ ├── CreateExport.vue # Export creation form
103
+ └── Logout.vue # Logout handler
104
+ ```
105
+
106
+ ## Key Code Examples
107
+
108
+ ### Initializing the Toolkit (main.ts)
109
+
110
+ ```typescript
111
+ import { initEenToolkit } from 'een-api-toolkit'
112
+
113
+ initEenToolkit({
114
+ proxyUrl: import.meta.env.VITE_PROXY_URL,
115
+ clientId: import.meta.env.VITE_EEN_CLIENT_ID,
116
+ storageStrategy: 'memory', // Maximum security - tokens lost on refresh
117
+ debug: true
118
+ })
119
+ ```
120
+
121
+ ### Listing Jobs with State Filtering (Jobs.vue)
122
+
123
+ ```typescript
124
+ import { ref } from 'vue'
125
+ import { listJobs, type Job, type JobState, type ListJobsParams } from 'een-api-toolkit'
126
+
127
+ const jobs = ref<Job[]>([])
128
+ const selectedStates = ref<JobState[]>([])
129
+
130
+ async function fetchJobs(params: ListJobsParams) {
131
+ const mergedParams: ListJobsParams = { ...params }
132
+ if (selectedStates.value.length > 0) {
133
+ mergedParams.state__in = selectedStates.value
134
+ }
135
+
136
+ const result = await listJobs(mergedParams)
137
+ if (result.error) {
138
+ // Handle error
139
+ } else {
140
+ jobs.value = result.data.results
141
+ }
142
+ }
143
+ ```
144
+
145
+ ### Polling Job Progress (JobDetail.vue)
146
+
147
+ ```typescript
148
+ import { ref, onMounted, onUnmounted } from 'vue'
149
+ import { getJob, type Job } from 'een-api-toolkit'
150
+
151
+ const job = ref<Job | null>(null)
152
+ let pollInterval: ReturnType<typeof setInterval> | null = null
153
+
154
+ async function fetchJob(jobId: string) {
155
+ const result = await getJob(jobId)
156
+ if (result.error) {
157
+ // Handle error
158
+ } else {
159
+ job.value = result.data
160
+
161
+ // Auto-start polling if job is in progress
162
+ if (['pending', 'started'].includes(result.data.state)) {
163
+ startPolling()
164
+ } else {
165
+ stopPolling()
166
+ }
167
+ }
168
+ }
169
+
170
+ function startPolling() {
171
+ if (pollInterval) return
172
+ pollInterval = setInterval(() => fetchJob(jobId), 3000)
173
+ }
174
+
175
+ function stopPolling() {
176
+ if (pollInterval) {
177
+ clearInterval(pollInterval)
178
+ pollInterval = null
179
+ }
180
+ }
181
+
182
+ onUnmounted(() => stopPolling())
183
+ ```
184
+
185
+ ### Creating an Export (CreateExport.vue)
186
+
187
+ ```typescript
188
+ import { createExportJob, formatTimestamp, type ExportType } from 'een-api-toolkit'
189
+
190
+ async function handleSubmit() {
191
+ const endTime = new Date()
192
+ const startTime = new Date(endTime.getTime() - duration.value * 60 * 1000)
193
+
194
+ const result = await createExportJob({
195
+ name: exportName.value || `Export - ${new Date().toLocaleString()}`,
196
+ type: exportType.value,
197
+ cameraId: selectedCamera.value,
198
+ startTimestamp: formatTimestamp(startTime.toISOString()),
199
+ endTimestamp: formatTimestamp(endTime.toISOString())
200
+ })
201
+
202
+ if (result.error) {
203
+ // Handle error
204
+ } else {
205
+ // Navigate to job detail page
206
+ router.push(`/jobs/${result.data.id}`)
207
+ }
208
+ }
209
+ ```
210
+
211
+ ### Downloading Files (Files.vue)
212
+
213
+ ```typescript
214
+ import { downloadFile, type EenFile } from 'een-api-toolkit'
215
+
216
+ async function handleDownload(file: EenFile) {
217
+ const result = await downloadFile(file.id)
218
+
219
+ if (result.error) {
220
+ // Handle error
221
+ } else {
222
+ // Create download link
223
+ const url = URL.createObjectURL(result.data.blob)
224
+ const a = document.createElement('a')
225
+ a.href = url
226
+ a.download = result.data.filename || file.name
227
+ a.click()
228
+ URL.revokeObjectURL(url)
229
+ }
230
+ }
231
+ ```
232
+
233
+ ### Auth Guard (router/index.ts)
234
+
235
+ ```typescript
236
+ router.beforeEach((to, from, next) => {
237
+ const authStore = useAuthStore()
238
+
239
+ if (to.meta.requiresAuth && !authStore.isAuthenticated) {
240
+ next('/login')
241
+ } else {
242
+ next()
243
+ }
244
+ })
245
+ ```
@@ -0,0 +1,79 @@
1
+ import { test, expect } from '@playwright/test'
2
+
3
+ test.describe('Vue Jobs Example - App', () => {
4
+ test.beforeEach(async ({ page }) => {
5
+ await page.goto('/')
6
+ })
7
+
8
+ test('app loads with correct title', async ({ page }) => {
9
+ await expect(page).toHaveTitle(/EEN Jobs/)
10
+ })
11
+
12
+ test('header displays app name', async ({ page }) => {
13
+ await expect(page.locator('[data-testid="app-title"]')).toHaveText('EEN Jobs Example')
14
+ })
15
+
16
+ test('navigation shows Home and Login links when not authenticated', async ({ page }) => {
17
+ // Home link should be visible
18
+ await expect(page.locator('[data-testid="nav-home"]')).toBeVisible()
19
+
20
+ // Login link should be visible (not authenticated)
21
+ await expect(page.locator('[data-testid="nav-login"]')).toBeVisible()
22
+
23
+ // Jobs, Files, Create Export, and Logout should NOT be visible (requires auth)
24
+ await expect(page.locator('[data-testid="nav-jobs"]')).not.toBeVisible()
25
+ await expect(page.locator('[data-testid="nav-files"]')).not.toBeVisible()
26
+ await expect(page.locator('[data-testid="nav-create-export"]')).not.toBeVisible()
27
+ await expect(page.locator('[data-testid="nav-logout"]')).not.toBeVisible()
28
+ })
29
+
30
+ test('home page shows not logged in message', async ({ page }) => {
31
+ await expect(page.locator('[data-testid="not-authenticated"]')).toBeVisible()
32
+ await expect(page.locator('[data-testid="not-authenticated-message"]')).toBeVisible()
33
+ await expect(page.locator('[data-testid="login-button"]')).toBeVisible()
34
+ })
35
+
36
+ test('login page displays login button', async ({ page }) => {
37
+ await page.goto('/login')
38
+
39
+ await expect(page.locator('[data-testid="login-title"]')).toHaveText('Login')
40
+ await expect(page.locator('[data-testid="login-button"]')).toBeVisible()
41
+ })
42
+
43
+ test('protected route /jobs redirects to login', async ({ page }) => {
44
+ await page.goto('/jobs')
45
+
46
+ // Should be redirected to login page
47
+ await page.waitForURL('/login')
48
+ await expect(page).toHaveURL('/login')
49
+ await expect(page.locator('[data-testid="login-title"]')).toHaveText('Login')
50
+ })
51
+
52
+ test('protected route /files redirects to login', async ({ page }) => {
53
+ await page.goto('/files')
54
+
55
+ // Should be redirected to login page
56
+ await page.waitForURL('/login')
57
+ await expect(page).toHaveURL('/login')
58
+ })
59
+
60
+ test('protected route /create-export redirects to login', async ({ page }) => {
61
+ await page.goto('/create-export')
62
+
63
+ // Should be redirected to login page
64
+ await page.waitForURL('/login')
65
+ await expect(page).toHaveURL('/login')
66
+ })
67
+
68
+ test('navigation between pages works', async ({ page }) => {
69
+ // Click Login link
70
+ await page.click('[data-testid="nav-login"]')
71
+ await page.waitForURL('/login')
72
+ await expect(page).toHaveURL('/login')
73
+
74
+ // Click Home link
75
+ await page.click('[data-testid="nav-home"]')
76
+ await page.waitForURL('/')
77
+ await expect(page).toHaveURL('/')
78
+ })
79
+ })