een-api-toolkit 0.0.8 → 0.0.13
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 +257 -146
- package/README.md +108 -230
- package/docs/AI-CONTEXT.md +767 -0
- package/examples/vue-basic/.env.example +12 -0
- package/examples/vue-basic/README.md +146 -0
- package/examples/vue-basic/e2e/app.spec.ts +61 -0
- package/examples/vue-basic/e2e/auth.spec.ts +260 -0
- package/examples/vue-basic/index.html +13 -0
- package/examples/vue-basic/package-lock.json +1583 -0
- package/examples/vue-basic/package.json +28 -0
- package/examples/vue-basic/playwright.config.ts +47 -0
- package/examples/vue-basic/src/App.vue +108 -0
- package/examples/vue-basic/src/main.ts +23 -0
- package/examples/vue-basic/src/router/index.ts +61 -0
- package/examples/vue-basic/src/views/Callback.vue +76 -0
- package/examples/vue-basic/src/views/Home.vue +105 -0
- package/examples/vue-basic/src/views/Login.vue +33 -0
- package/examples/vue-basic/src/views/Logout.vue +59 -0
- package/examples/vue-basic/src/views/Users.vue +106 -0
- package/examples/vue-basic/src/vite-env.d.ts +12 -0
- package/examples/vue-basic/tsconfig.json +21 -0
- package/examples/vue-basic/tsconfig.node.json +10 -0
- package/examples/vue-basic/vite.config.ts +12 -0
- package/package.json +3 -1
|
@@ -0,0 +1,767 @@
|
|
|
1
|
+
# EEN API Toolkit - AI Reference
|
|
2
|
+
|
|
3
|
+
> **Version:** 0.0.13
|
|
4
|
+
> **Generated:** 2025-12-29
|
|
5
|
+
>
|
|
6
|
+
> This file is optimized for AI assistants. It contains all API signatures,
|
|
7
|
+
> types, and usage patterns in a single, parseable document.
|
|
8
|
+
>
|
|
9
|
+
> For the full EEN API documentation, see the
|
|
10
|
+
> [Eagle Eye Networks Developer Portal](https://developer.eagleeyenetworks.com).
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Quick Reference
|
|
15
|
+
|
|
16
|
+
### Configuration
|
|
17
|
+
|
|
18
|
+
| Function | Purpose |
|
|
19
|
+
|----------|---------|
|
|
20
|
+
| `initEenToolkit(config)` | Initialize the toolkit with proxy URL and client ID |
|
|
21
|
+
|
|
22
|
+
### Authentication Functions
|
|
23
|
+
|
|
24
|
+
| Function | Purpose | Returns |
|
|
25
|
+
|----------|---------|---------|
|
|
26
|
+
| `getAuthUrl()` | Generate OAuth authorization URL | `string` |
|
|
27
|
+
| `handleAuthCallback(code, state)` | Exchange auth code for token | `Result<TokenResponse>` |
|
|
28
|
+
| `refreshToken()` | Refresh the access token | `Result<{accessToken, expiresIn}>` |
|
|
29
|
+
| `revokeToken()` | Revoke token and logout | `Result<void>` |
|
|
30
|
+
|
|
31
|
+
### User Functions
|
|
32
|
+
|
|
33
|
+
| Function | Purpose | Returns |
|
|
34
|
+
|----------|---------|---------|
|
|
35
|
+
| `getCurrentUser()` | Get current user profile | `Result<UserProfile>` |
|
|
36
|
+
| `getUsers(params?)` | List all users (paginated) | `Result<PaginatedResult<User>>` |
|
|
37
|
+
| `getUser(userId, params?)` | Get a specific user | `Result<User>` |
|
|
38
|
+
|
|
39
|
+
### Vue 3 Composables
|
|
40
|
+
|
|
41
|
+
| Composable | Purpose | Returns |
|
|
42
|
+
|------------|---------|---------|
|
|
43
|
+
| `useCurrentUser(options?)` | Reactive current user | `{ user, loading, error, refresh }` |
|
|
44
|
+
| `useUsers(params?, options?)` | Reactive user list with pagination | `{ users, loading, hasNextPage, fetchNextPage, ... }` |
|
|
45
|
+
| `useUser(userId, options?)` | Reactive single user | `{ user, loading, error, refresh }` |
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Critical Requirements
|
|
50
|
+
|
|
51
|
+
### OAuth Redirect URI (IMPORTANT)
|
|
52
|
+
|
|
53
|
+
The EEN Identity Provider performs an **exact string match** on the redirect URI. Applications MUST follow these rules:
|
|
54
|
+
|
|
55
|
+
| Requirement | Correct | Incorrect |
|
|
56
|
+
|-------------|---------|-----------|
|
|
57
|
+
| Host | `127.0.0.1` | `localhost` |
|
|
58
|
+
| Path | None (root path only) | `/callback` |
|
|
59
|
+
| Trailing slash | No | `http://127.0.0.1:3333/` |
|
|
60
|
+
|
|
61
|
+
**The only valid redirect URI is: `http://127.0.0.1:3333`**
|
|
62
|
+
|
|
63
|
+
**Configure at:** [EEN Developer Portal - My Application](https://developer.eagleeyenetworks.com/page/my-application)
|
|
64
|
+
|
|
65
|
+
### Application Requirements
|
|
66
|
+
|
|
67
|
+
1. **Handle OAuth callbacks on the root path (`/`)** - not `/callback`
|
|
68
|
+
2. **Run dev server on `127.0.0.1`** - not `localhost`
|
|
69
|
+
3. **Register exactly `http://127.0.0.1:3333` with EEN at the Developer Portal**
|
|
70
|
+
|
|
71
|
+
### Router Pattern for OAuth Callbacks
|
|
72
|
+
|
|
73
|
+
The root path must detect OAuth params and handle the callback:
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
// router/index.ts
|
|
77
|
+
{
|
|
78
|
+
path: '/',
|
|
79
|
+
name: 'home',
|
|
80
|
+
component: Home,
|
|
81
|
+
beforeEnter: (to, _from, next) => {
|
|
82
|
+
// If URL has OAuth params, redirect to callback handler
|
|
83
|
+
if (to.query.code && to.query.state) {
|
|
84
|
+
next({ name: 'callback', query: to.query })
|
|
85
|
+
} else {
|
|
86
|
+
next()
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Vite Dev Server Configuration
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
// vite.config.ts
|
|
96
|
+
export default defineConfig({
|
|
97
|
+
server: {
|
|
98
|
+
host: '127.0.0.1', // MUST use 127.0.0.1, not localhost
|
|
99
|
+
port: 3333,
|
|
100
|
+
strictPort: true
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Common OAuth Errors
|
|
106
|
+
|
|
107
|
+
| Error | Cause | Solution |
|
|
108
|
+
|-------|-------|----------|
|
|
109
|
+
| "Redirect URI mismatch" | URI doesn't match exactly | Use `http://127.0.0.1:3333` (no path, no trailing slash) |
|
|
110
|
+
| Redirected back to login | Router guard blocks callback | Allow OAuth params through on root path |
|
|
111
|
+
| Callback not processed | Wrong path or host | Handle callback on `/`, use `127.0.0.1` |
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Core Types
|
|
116
|
+
|
|
117
|
+
### Result<T>
|
|
118
|
+
|
|
119
|
+
All API functions return a `Result<T>` type - they never throw exceptions.
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
type Result<T> =
|
|
123
|
+
| { data: T; error: null } // Success
|
|
124
|
+
| { data: null; error: EenError } // Failure
|
|
125
|
+
|
|
126
|
+
interface EenError {
|
|
127
|
+
code: ErrorCode
|
|
128
|
+
message: string
|
|
129
|
+
status?: number
|
|
130
|
+
details?: unknown
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
type ErrorCode =
|
|
134
|
+
| 'AUTH_REQUIRED' // No valid token - redirect to login
|
|
135
|
+
| 'AUTH_FAILED' // Authentication failed
|
|
136
|
+
| 'TOKEN_EXPIRED' // Token expired - will auto-refresh
|
|
137
|
+
| 'API_ERROR' // EEN API returned an error
|
|
138
|
+
| 'NETWORK_ERROR' // Network request failed
|
|
139
|
+
| 'VALIDATION_ERROR' // Invalid parameters
|
|
140
|
+
| 'NOT_FOUND' // Resource not found (404)
|
|
141
|
+
| 'FORBIDDEN' // Access denied (403)
|
|
142
|
+
| 'RATE_LIMITED' // Too many requests (429)
|
|
143
|
+
| 'UNKNOWN_ERROR' // Unexpected error
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Pagination Types
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
interface PaginationParams {
|
|
150
|
+
pageSize?: number // Results per page (default varies, typically 100)
|
|
151
|
+
pageToken?: string // Token for fetching a specific page
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
interface PaginatedResult<T> {
|
|
155
|
+
results: T[]
|
|
156
|
+
nextPageToken?: string // Token for next page (undefined if last page)
|
|
157
|
+
prevPageToken?: string // Token for previous page
|
|
158
|
+
totalSize?: number // Total count (not always provided)
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Configuration Type
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
interface EenToolkitConfig {
|
|
166
|
+
proxyUrl?: string // OAuth proxy URL (required for API calls)
|
|
167
|
+
clientId?: string // EEN OAuth client ID
|
|
168
|
+
redirectUri?: string // OAuth redirect URI (default: http://127.0.0.1:3333)
|
|
169
|
+
debug?: boolean // Enable debug logging
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Entity Types
|
|
176
|
+
|
|
177
|
+
### User
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
interface User {
|
|
181
|
+
id: string
|
|
182
|
+
email: string
|
|
183
|
+
firstName: string
|
|
184
|
+
lastName: string
|
|
185
|
+
accountId?: string
|
|
186
|
+
timeZone?: string // IANA timezone (e.g., "America/Los_Angeles")
|
|
187
|
+
language?: string // ISO 639-1 code (e.g., "en")
|
|
188
|
+
phone?: string
|
|
189
|
+
mobilePhone?: string
|
|
190
|
+
permissions?: string[] // Requires include: ['permissions']
|
|
191
|
+
lastLogin?: string // ISO 8601 timestamp
|
|
192
|
+
isActive?: boolean
|
|
193
|
+
createdAt?: string
|
|
194
|
+
updatedAt?: string
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
interface UserProfile {
|
|
198
|
+
id: string
|
|
199
|
+
email: string
|
|
200
|
+
firstName: string
|
|
201
|
+
lastName: string
|
|
202
|
+
accountId?: string
|
|
203
|
+
timeZone?: string
|
|
204
|
+
language?: string
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Parameter Types
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
interface ListUsersParams {
|
|
212
|
+
pageSize?: number // Results per page (default: 100, max: 1000)
|
|
213
|
+
pageToken?: string // Pagination token
|
|
214
|
+
include?: string[] // Additional fields (e.g., ['permissions'])
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
interface GetUserParams {
|
|
218
|
+
include?: string[] // Additional fields to include
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## API Reference
|
|
225
|
+
|
|
226
|
+
### initEenToolkit
|
|
227
|
+
|
|
228
|
+
Initialize the toolkit. Call this before using any API functions.
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
import { initEenToolkit } from 'een-api-toolkit'
|
|
232
|
+
|
|
233
|
+
// In main.ts
|
|
234
|
+
initEenToolkit({
|
|
235
|
+
proxyUrl: import.meta.env.VITE_PROXY_URL,
|
|
236
|
+
clientId: import.meta.env.VITE_EEN_CLIENT_ID,
|
|
237
|
+
debug: true // optional
|
|
238
|
+
})
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### getAuthUrl
|
|
242
|
+
|
|
243
|
+
Generate the OAuth authorization URL. Redirect the user here to start login.
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
import { getAuthUrl } from 'een-api-toolkit'
|
|
247
|
+
|
|
248
|
+
function login() {
|
|
249
|
+
window.location.href = getAuthUrl()
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### handleAuthCallback
|
|
254
|
+
|
|
255
|
+
Handle the OAuth callback after user authorizes. Call this when user returns to your redirect URI.
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
import { handleAuthCallback } from 'een-api-toolkit'
|
|
259
|
+
|
|
260
|
+
// In your callback route handler
|
|
261
|
+
const url = new URL(window.location.href)
|
|
262
|
+
const code = url.searchParams.get('code')
|
|
263
|
+
const state = url.searchParams.get('state')
|
|
264
|
+
|
|
265
|
+
if (code && state) {
|
|
266
|
+
const { data, error } = await handleAuthCallback(code, state)
|
|
267
|
+
|
|
268
|
+
if (error) {
|
|
269
|
+
console.error('Auth failed:', error.message)
|
|
270
|
+
return
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// User is now authenticated
|
|
274
|
+
router.push('/dashboard')
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### getCurrentUser
|
|
279
|
+
|
|
280
|
+
Get the current authenticated user's profile.
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
import { getCurrentUser } from 'een-api-toolkit'
|
|
284
|
+
|
|
285
|
+
const { data, error } = await getCurrentUser()
|
|
286
|
+
|
|
287
|
+
if (error) {
|
|
288
|
+
if (error.code === 'AUTH_REQUIRED') {
|
|
289
|
+
router.push('/login')
|
|
290
|
+
}
|
|
291
|
+
return
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
console.log(`Welcome, ${data.firstName} ${data.lastName}`)
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### getUsers
|
|
298
|
+
|
|
299
|
+
List users with optional pagination.
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
import { getUsers } from 'een-api-toolkit'
|
|
303
|
+
|
|
304
|
+
// Basic usage
|
|
305
|
+
const { data, error } = await getUsers()
|
|
306
|
+
|
|
307
|
+
// With pagination
|
|
308
|
+
const { data } = await getUsers({ pageSize: 50 })
|
|
309
|
+
|
|
310
|
+
// Fetch all users
|
|
311
|
+
let allUsers: User[] = []
|
|
312
|
+
let pageToken: string | undefined
|
|
313
|
+
|
|
314
|
+
do {
|
|
315
|
+
const { data, error } = await getUsers({ pageSize: 100, pageToken })
|
|
316
|
+
if (error) break
|
|
317
|
+
allUsers.push(...data.results)
|
|
318
|
+
pageToken = data.nextPageToken
|
|
319
|
+
} while (pageToken)
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### getUser
|
|
323
|
+
|
|
324
|
+
Get a specific user by ID.
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
import { getUser } from 'een-api-toolkit'
|
|
328
|
+
|
|
329
|
+
const { data, error } = await getUser('user-id-123')
|
|
330
|
+
|
|
331
|
+
if (error) {
|
|
332
|
+
if (error.code === 'NOT_FOUND') {
|
|
333
|
+
console.log('User not found')
|
|
334
|
+
}
|
|
335
|
+
return
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// With permissions
|
|
339
|
+
const { data: userWithPerms } = await getUser('user-id-123', {
|
|
340
|
+
include: ['permissions']
|
|
341
|
+
})
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
## Vue 3 Composables
|
|
347
|
+
|
|
348
|
+
### useCurrentUser
|
|
349
|
+
|
|
350
|
+
Reactive composable for the current authenticated user.
|
|
351
|
+
|
|
352
|
+
```vue
|
|
353
|
+
<script setup>
|
|
354
|
+
import { useCurrentUser } from 'een-api-toolkit'
|
|
355
|
+
|
|
356
|
+
const { user, loading, error, refresh } = useCurrentUser()
|
|
357
|
+
|
|
358
|
+
// Options:
|
|
359
|
+
// { immediate: false } - don't fetch on mount
|
|
360
|
+
</script>
|
|
361
|
+
|
|
362
|
+
<template>
|
|
363
|
+
<div v-if="loading">Loading...</div>
|
|
364
|
+
<div v-else-if="error">Error: {{ error.message }}</div>
|
|
365
|
+
<div v-else-if="user">
|
|
366
|
+
<h1>Welcome, {{ user.firstName }}!</h1>
|
|
367
|
+
<button @click="refresh">Refresh</button>
|
|
368
|
+
</div>
|
|
369
|
+
</template>
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
**Returns:**
|
|
373
|
+
- `user: Ref<UserProfile | null>` - The user profile
|
|
374
|
+
- `loading: Ref<boolean>` - Fetch in progress
|
|
375
|
+
- `error: Ref<EenError | null>` - Last error
|
|
376
|
+
- `fetch(): Promise<Result<UserProfile>>` - Fetch user
|
|
377
|
+
- `refresh(): Promise<Result<UserProfile>>` - Alias for fetch
|
|
378
|
+
|
|
379
|
+
### useUsers
|
|
380
|
+
|
|
381
|
+
Reactive composable for listing users with pagination.
|
|
382
|
+
|
|
383
|
+
```vue
|
|
384
|
+
<script setup>
|
|
385
|
+
import { useUsers } from 'een-api-toolkit'
|
|
386
|
+
|
|
387
|
+
const {
|
|
388
|
+
users,
|
|
389
|
+
loading,
|
|
390
|
+
error,
|
|
391
|
+
hasNextPage,
|
|
392
|
+
fetchNextPage,
|
|
393
|
+
refresh
|
|
394
|
+
} = useUsers({ pageSize: 20 })
|
|
395
|
+
</script>
|
|
396
|
+
|
|
397
|
+
<template>
|
|
398
|
+
<ul>
|
|
399
|
+
<li v-for="user in users" :key="user.id">
|
|
400
|
+
{{ user.firstName }} {{ user.lastName }}
|
|
401
|
+
</li>
|
|
402
|
+
</ul>
|
|
403
|
+
<button v-if="hasNextPage" @click="fetchNextPage">Load More</button>
|
|
404
|
+
</template>
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
**Returns:**
|
|
408
|
+
- `users: Ref<User[]>` - Array of users
|
|
409
|
+
- `loading: Ref<boolean>` - Fetch in progress
|
|
410
|
+
- `error: Ref<EenError | null>` - Last error
|
|
411
|
+
- `hasNextPage: ComputedRef<boolean>` - More pages available
|
|
412
|
+
- `hasPrevPage: ComputedRef<boolean>` - Previous page available
|
|
413
|
+
- `nextPageToken: Ref<string | undefined>` - Next page token
|
|
414
|
+
- `totalSize: Ref<number | undefined>` - Total count
|
|
415
|
+
- `fetch(params?): Promise<Result>` - Fetch with params
|
|
416
|
+
- `refresh(): Promise<Result>` - Refresh current page
|
|
417
|
+
- `fetchNextPage(): Promise<Result | undefined>` - Fetch next page
|
|
418
|
+
- `fetchPrevPage(): Promise<Result | undefined>` - Fetch previous page
|
|
419
|
+
- `setParams(params): void` - Update default params
|
|
420
|
+
|
|
421
|
+
### useUser
|
|
422
|
+
|
|
423
|
+
Reactive composable for a single user by ID.
|
|
424
|
+
|
|
425
|
+
```vue
|
|
426
|
+
<script setup>
|
|
427
|
+
import { useUser } from 'een-api-toolkit'
|
|
428
|
+
import { useRoute } from 'vue-router'
|
|
429
|
+
|
|
430
|
+
const route = useRoute()
|
|
431
|
+
|
|
432
|
+
// Static ID
|
|
433
|
+
const { user, loading, error } = useUser('user-123')
|
|
434
|
+
|
|
435
|
+
// Reactive ID from route
|
|
436
|
+
const { user: routeUser } = useUser(() => route.params.id as string)
|
|
437
|
+
|
|
438
|
+
// With options
|
|
439
|
+
const { user: userWithPerms } = useUser('user-123', {
|
|
440
|
+
include: ['permissions']
|
|
441
|
+
})
|
|
442
|
+
</script>
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
**Returns:**
|
|
446
|
+
- `user: Ref<User | null>` - The user
|
|
447
|
+
- `loading: Ref<boolean>` - Fetch in progress
|
|
448
|
+
- `error: Ref<EenError | null>` - Last error
|
|
449
|
+
- `fetch(params?): Promise<Result<User>>` - Fetch user
|
|
450
|
+
- `refresh(): Promise<Result<User>>` - Refresh user
|
|
451
|
+
|
|
452
|
+
---
|
|
453
|
+
|
|
454
|
+
## Common Patterns
|
|
455
|
+
|
|
456
|
+
### Error Handling
|
|
457
|
+
|
|
458
|
+
```typescript
|
|
459
|
+
// All functions return Result<T>, never throw
|
|
460
|
+
const { data, error } = await getUsers()
|
|
461
|
+
|
|
462
|
+
if (error) {
|
|
463
|
+
switch (error.code) {
|
|
464
|
+
case 'AUTH_REQUIRED':
|
|
465
|
+
router.push('/login')
|
|
466
|
+
break
|
|
467
|
+
case 'NETWORK_ERROR':
|
|
468
|
+
showRetryDialog()
|
|
469
|
+
break
|
|
470
|
+
case 'RATE_LIMITED':
|
|
471
|
+
await sleep(1000)
|
|
472
|
+
return retry()
|
|
473
|
+
default:
|
|
474
|
+
showError(error.message)
|
|
475
|
+
}
|
|
476
|
+
return
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// TypeScript knows data is not null here
|
|
480
|
+
processUsers(data.results)
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
### Pagination
|
|
484
|
+
|
|
485
|
+
```typescript
|
|
486
|
+
// Manual pagination - fetch all
|
|
487
|
+
async function fetchAllUsers(): Promise<User[]> {
|
|
488
|
+
const allUsers: User[] = []
|
|
489
|
+
let pageToken: string | undefined
|
|
490
|
+
|
|
491
|
+
do {
|
|
492
|
+
const { data, error } = await getUsers({ pageSize: 100, pageToken })
|
|
493
|
+
if (error) break // Stop on error, return what we have
|
|
494
|
+
allUsers.push(...data.results)
|
|
495
|
+
pageToken = data.nextPageToken
|
|
496
|
+
} while (pageToken)
|
|
497
|
+
|
|
498
|
+
return allUsers
|
|
499
|
+
}
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
### Auth Guard (Vue Router)
|
|
503
|
+
|
|
504
|
+
```typescript
|
|
505
|
+
import { useAuthStore } from 'een-api-toolkit'
|
|
506
|
+
|
|
507
|
+
router.beforeEach((to, from, next) => {
|
|
508
|
+
const authStore = useAuthStore()
|
|
509
|
+
|
|
510
|
+
if (to.meta.requiresAuth && !authStore.isAuthenticated) {
|
|
511
|
+
next('/login')
|
|
512
|
+
} else {
|
|
513
|
+
next()
|
|
514
|
+
}
|
|
515
|
+
})
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
### Composable with Conditional Fetch
|
|
519
|
+
|
|
520
|
+
```vue
|
|
521
|
+
<script setup>
|
|
522
|
+
import { useCurrentUser } from 'een-api-toolkit'
|
|
523
|
+
import { onMounted } from 'vue'
|
|
524
|
+
|
|
525
|
+
// Don't fetch on mount
|
|
526
|
+
const { user, fetch } = useCurrentUser({ immediate: false })
|
|
527
|
+
|
|
528
|
+
onMounted(async () => {
|
|
529
|
+
// Only fetch if some condition is met
|
|
530
|
+
if (shouldFetchUser()) {
|
|
531
|
+
await fetch()
|
|
532
|
+
}
|
|
533
|
+
})
|
|
534
|
+
</script>
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
### In-Place Login (Single Page Callback)
|
|
538
|
+
|
|
539
|
+
When handling the OAuth callback on the same page that displays user data (without navigation), you must manually refresh the composable after authentication:
|
|
540
|
+
|
|
541
|
+
> **⚠️ Important:** When handling OAuth callbacks on the same page, you must call `refresh()` after `handleAuthCallback()` to update the user data.
|
|
542
|
+
|
|
543
|
+
```vue
|
|
544
|
+
<script setup lang="ts">
|
|
545
|
+
import { onMounted } from 'vue'
|
|
546
|
+
import { useCurrentUser, handleAuthCallback } from 'een-api-toolkit'
|
|
547
|
+
|
|
548
|
+
// Composable initializes before token exists
|
|
549
|
+
const { user, loading, error, refresh } = useCurrentUser({ immediate: false })
|
|
550
|
+
|
|
551
|
+
onMounted(async () => {
|
|
552
|
+
const urlParams = new URLSearchParams(window.location.search)
|
|
553
|
+
const code = urlParams.get('code')
|
|
554
|
+
const state = urlParams.get('state')
|
|
555
|
+
|
|
556
|
+
if (code && state) {
|
|
557
|
+
const result = await handleAuthCallback(code, state)
|
|
558
|
+
|
|
559
|
+
if (result.error) {
|
|
560
|
+
console.error('Login failed:', result.error.message)
|
|
561
|
+
return
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Clean URL after successful auth
|
|
565
|
+
window.history.replaceState({}, document.title, window.location.pathname)
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// Refresh user data - runs after successful auth callback,
|
|
569
|
+
// or on initial load if no callback was processed
|
|
570
|
+
await refresh()
|
|
571
|
+
})
|
|
572
|
+
</script>
|
|
573
|
+
|
|
574
|
+
<template>
|
|
575
|
+
<div v-if="loading">Loading...</div>
|
|
576
|
+
<div v-else-if="error">{{ error.message }}</div>
|
|
577
|
+
<div v-else-if="user">Welcome, {{ user.firstName }}!</div>
|
|
578
|
+
<div v-else>Please log in</div>
|
|
579
|
+
</template>
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
**Key Points:**
|
|
583
|
+
- `useCurrentUser()` initializes with the auth state at the moment it's called
|
|
584
|
+
- `handleAuthCallback()` updates the token but doesn't trigger composable re-fetch
|
|
585
|
+
- Always call `refresh()` after auth state changes if staying on the same page
|
|
586
|
+
|
|
587
|
+
---
|
|
588
|
+
|
|
589
|
+
## Anti-Patterns (What NOT to Do)
|
|
590
|
+
|
|
591
|
+
### DON'T: Use try/catch for API errors
|
|
592
|
+
|
|
593
|
+
```typescript
|
|
594
|
+
// WRONG - functions don't throw
|
|
595
|
+
try {
|
|
596
|
+
const users = await getUsers()
|
|
597
|
+
} catch (e) {
|
|
598
|
+
// This will never catch API errors!
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// CORRECT
|
|
602
|
+
const { data, error } = await getUsers()
|
|
603
|
+
if (error) handleError(error)
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
### DON'T: Ignore the error check
|
|
607
|
+
|
|
608
|
+
```typescript
|
|
609
|
+
// WRONG - data might be null
|
|
610
|
+
const { data } = await getUsers()
|
|
611
|
+
data.results.forEach(...) // TypeError if error occurred!
|
|
612
|
+
|
|
613
|
+
// CORRECT
|
|
614
|
+
const { data, error } = await getUsers()
|
|
615
|
+
if (error) return
|
|
616
|
+
data.results.forEach(...) // Safe - TypeScript knows data is not null
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
### DON'T: Call initEenToolkit multiple times
|
|
620
|
+
|
|
621
|
+
```typescript
|
|
622
|
+
// WRONG - calling in component
|
|
623
|
+
export default {
|
|
624
|
+
setup() {
|
|
625
|
+
initEenToolkit({ ... }) // Called every time component mounts!
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// CORRECT - call once in main.ts
|
|
630
|
+
// main.ts
|
|
631
|
+
initEenToolkit({ ... })
|
|
632
|
+
app.mount('#app')
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
### DON'T: Access data before checking error
|
|
636
|
+
|
|
637
|
+
```typescript
|
|
638
|
+
// WRONG - unsafe access
|
|
639
|
+
const { data, error } = await getUser(id)
|
|
640
|
+
console.log(data.email) // TypeError if error!
|
|
641
|
+
if (error) { ... }
|
|
642
|
+
|
|
643
|
+
// CORRECT - check error first
|
|
644
|
+
const { data, error } = await getUser(id)
|
|
645
|
+
if (error) {
|
|
646
|
+
console.error(error.message)
|
|
647
|
+
return
|
|
648
|
+
}
|
|
649
|
+
console.log(data.email) // Safe
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
---
|
|
653
|
+
|
|
654
|
+
## Setup Guide
|
|
655
|
+
|
|
656
|
+
### Installation
|
|
657
|
+
|
|
658
|
+
```bash
|
|
659
|
+
npm install een-api-toolkit
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
### Prerequisites (IMPORTANT)
|
|
663
|
+
|
|
664
|
+
The toolkit uses **Pinia for state management internally**. You must install and configure Pinia before initializing the toolkit:
|
|
665
|
+
|
|
666
|
+
```bash
|
|
667
|
+
npm install pinia
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
**Pinia must be installed on the Vue app instance BEFORE calling `initEenToolkit()` or using any composables** (`useCurrentUser`, `useUsers`, `useUser`). Failing to do so will result in:
|
|
671
|
+
|
|
672
|
+
```
|
|
673
|
+
Error: [🍍]: "getActivePinia()" was called but there was no active Pinia.
|
|
674
|
+
Are you trying to use a store before calling "app.use(pinia)"?
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
### Environment Variables
|
|
678
|
+
|
|
679
|
+
Create a `.env` file:
|
|
680
|
+
|
|
681
|
+
```env
|
|
682
|
+
VITE_PROXY_URL=https://your-proxy.workers.dev
|
|
683
|
+
VITE_EEN_CLIENT_ID=your-een-client-id
|
|
684
|
+
# IMPORTANT: EEN IDP only permits this exact redirect URI
|
|
685
|
+
VITE_REDIRECT_URI=http://127.0.0.1:3333
|
|
686
|
+
VITE_DEBUG=true
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
> **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.
|
|
690
|
+
|
|
691
|
+
### Basic Setup (main.ts)
|
|
692
|
+
|
|
693
|
+
```typescript
|
|
694
|
+
import { createApp } from 'vue'
|
|
695
|
+
import { createPinia } from 'pinia'
|
|
696
|
+
import { initEenToolkit } from 'een-api-toolkit'
|
|
697
|
+
import App from './App.vue'
|
|
698
|
+
|
|
699
|
+
const app = createApp(App)
|
|
700
|
+
const pinia = createPinia()
|
|
701
|
+
|
|
702
|
+
// IMPORTANT: Install Pinia BEFORE initializing the toolkit
|
|
703
|
+
app.use(pinia)
|
|
704
|
+
|
|
705
|
+
// Now initialize the toolkit (Pinia must already be installed)
|
|
706
|
+
initEenToolkit({
|
|
707
|
+
proxyUrl: import.meta.env.VITE_PROXY_URL,
|
|
708
|
+
clientId: import.meta.env.VITE_EEN_CLIENT_ID,
|
|
709
|
+
redirectUri: import.meta.env.VITE_REDIRECT_URI,
|
|
710
|
+
debug: import.meta.env.VITE_DEBUG === 'true'
|
|
711
|
+
})
|
|
712
|
+
|
|
713
|
+
app.mount('#app')
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
### OAuth Callback Route
|
|
717
|
+
|
|
718
|
+
```typescript
|
|
719
|
+
// router/index.ts
|
|
720
|
+
{
|
|
721
|
+
path: '/callback',
|
|
722
|
+
component: () => import('./views/Callback.vue')
|
|
723
|
+
}
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
```vue
|
|
727
|
+
<!-- views/Callback.vue -->
|
|
728
|
+
<script setup>
|
|
729
|
+
import { onMounted } from 'vue'
|
|
730
|
+
import { useRouter } from 'vue-router'
|
|
731
|
+
import { handleAuthCallback } from 'een-api-toolkit'
|
|
732
|
+
|
|
733
|
+
const router = useRouter()
|
|
734
|
+
|
|
735
|
+
onMounted(async () => {
|
|
736
|
+
const url = new URL(window.location.href)
|
|
737
|
+
const code = url.searchParams.get('code')
|
|
738
|
+
const state = url.searchParams.get('state')
|
|
739
|
+
|
|
740
|
+
if (!code || !state) {
|
|
741
|
+
router.push('/login?error=missing_params')
|
|
742
|
+
return
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
const { error } = await handleAuthCallback(code, state)
|
|
746
|
+
|
|
747
|
+
if (error) {
|
|
748
|
+
router.push(`/login?error=${error.code}`)
|
|
749
|
+
return
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
router.push('/dashboard')
|
|
753
|
+
})
|
|
754
|
+
</script>
|
|
755
|
+
|
|
756
|
+
<template>
|
|
757
|
+
<div>Authenticating...</div>
|
|
758
|
+
</template>
|
|
759
|
+
```
|
|
760
|
+
|
|
761
|
+
---
|
|
762
|
+
|
|
763
|
+
## External Resources
|
|
764
|
+
|
|
765
|
+
- [Eagle Eye Networks Developer Portal](https://developer.eagleeyenetworks.com)
|
|
766
|
+
- [EEN API v3.0 Reference](https://developer.eagleeyenetworks.com/reference/using-the-api)
|
|
767
|
+
- [GitHub Repository](https://github.com/klaushofrichter/een-api-toolkit)
|