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 +9 -40
- package/README.md +40 -0
- package/docs/AI-CONTEXT.md +148 -177
- package/package.json +1 -1
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.
|
|
5
|
+
## [0.3.8] - 2026-01-09
|
|
6
6
|
|
|
7
7
|
### Release Summary
|
|
8
8
|
|
|
9
|
-
|
|
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:
|
|
14
|
+
- feat: Enhance sync-secrets.sh to sync .env to example apps
|
|
48
15
|
|
|
49
16
|
#### Bug Fixes
|
|
50
|
-
- fix:
|
|
51
|
-
|
|
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.
|
|
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-
|
|
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/)
|
package/docs/AI-CONTEXT.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# EEN API Toolkit - AI Reference
|
|
2
2
|
|
|
3
|
-
> **Version:** 0.3.
|
|
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
|
|
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'
|
|
249
|
-
| 'AUTH_FAILED'
|
|
250
|
-
| 'TOKEN_EXPIRED'
|
|
251
|
-
| 'API_ERROR'
|
|
252
|
-
| 'NETWORK_ERROR'
|
|
253
|
-
| 'VALIDATION_ERROR'
|
|
254
|
-
| 'NOT_FOUND'
|
|
255
|
-
| 'FORBIDDEN'
|
|
256
|
-
| 'RATE_LIMITED'
|
|
257
|
-
| '
|
|
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
|
|
281
|
-
clientId?: string
|
|
282
|
-
redirectUri?: string
|
|
283
|
-
|
|
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)
|