een-api-toolkit 0.0.5

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Klaus Hofrichter
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,321 @@
1
+ # een-api-toolkit
2
+
3
+ A TypeScript library implementing the Eagle Eye Networks (EEN) Video platform API v3.0 for Vue 3 Composition API applications.
4
+
5
+ This repository is provided as-is without any warranty, functionality guarantee, or assurance of availability. This repository uses EEN services, but it is not associated with EEN.
6
+
7
+ > **Note:** Work in progress - do not use in production yet.
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ npm install een-api-toolkit
13
+ ```
14
+
15
+ **Development (from source):**
16
+ ```bash
17
+ git clone https://github.com/klaushofrichter/een-api-toolkit.git
18
+ cd een-api-toolkit
19
+ npm install
20
+ npm run build
21
+ npm link
22
+
23
+ # In your project:
24
+ npm link een-api-toolkit
25
+ ```
26
+
27
+ ## Setup
28
+
29
+ ```typescript
30
+ // main.ts
31
+ import { createApp } from 'vue'
32
+ import { createPinia } from 'pinia'
33
+ import { initEenToolkit } from 'een-api-toolkit'
34
+ import App from './App.vue'
35
+
36
+ const app = createApp(App)
37
+ app.use(createPinia())
38
+
39
+ // Initialize toolkit with proxy URL from environment
40
+ initEenToolkit({
41
+ proxyUrl: import.meta.env.VITE_PROXY_URL
42
+ })
43
+
44
+ app.mount('#app')
45
+ ```
46
+
47
+ ## Authentication
48
+
49
+ The toolkit uses OAuth via a proxy server. Your app handles the redirect flow:
50
+
51
+ ```typescript
52
+ // Login.vue
53
+ import { getAuthUrl, handleAuthCallback } from 'een-api-toolkit'
54
+
55
+ // Redirect user to EEN login
56
+ const login = () => {
57
+ window.location.href = getAuthUrl()
58
+ }
59
+
60
+ // Handle OAuth callback (after redirect back)
61
+ const onCallback = async (code: string, state: string) => {
62
+ const { data, error } = await handleAuthCallback(code, state)
63
+ if (error) {
64
+ console.error('Auth failed:', error.message)
65
+ return
66
+ }
67
+ // User is now authenticated, token stored in Pinia
68
+ router.push('/dashboard')
69
+ }
70
+ ```
71
+
72
+ ## Usage
73
+
74
+ ### Vue 3 Composables (Reactive)
75
+
76
+ ```vue
77
+ <script setup>
78
+ import { useCurrentUser, useUsers } from 'een-api-toolkit'
79
+
80
+ // Get current authenticated user
81
+ const { user, loading, error, refresh } = useCurrentUser()
82
+
83
+ // List all users with pagination
84
+ const { users, loading: usersLoading, hasNextPage, fetchNextPage } = useUsers()
85
+ </script>
86
+
87
+ <template>
88
+ <div v-if="loading">Loading...</div>
89
+ <div v-else-if="error">Error: {{ error.message }}</div>
90
+ <div v-else>
91
+ <h1>Welcome, {{ user.firstName }}</h1>
92
+ <h2>All Users:</h2>
93
+ <ul>
94
+ <li v-for="u in users" :key="u.id">{{ u.email }}</li>
95
+ </ul>
96
+ <button v-if="hasNextPage" @click="fetchNextPage">Load More</button>
97
+ <button @click="refresh">Refresh</button>
98
+ </div>
99
+ </template>
100
+ ```
101
+
102
+ ### Plain Functions (Framework-Agnostic)
103
+
104
+ ```typescript
105
+ import { getUsers, getCameras, getBridges } from 'een-api-toolkit'
106
+
107
+ const fetchData = async () => {
108
+ const { data: users, error } = await getUsers()
109
+ if (error) {
110
+ console.error(error.code, error.message)
111
+ return
112
+ }
113
+ console.log('Users:', users)
114
+ }
115
+
116
+ // With pagination
117
+ const { data, error } = await getCameras({
118
+ pageSize: 50,
119
+ pageToken: 'next-page-token'
120
+ })
121
+ ```
122
+
123
+ ## Error Handling
124
+
125
+ All functions return `{data, error}` objects - they never throw exceptions:
126
+
127
+ ```typescript
128
+ const { data, error } = await getCameras()
129
+
130
+ if (error) {
131
+ switch (error.code) {
132
+ case 'AUTH_REQUIRED':
133
+ router.push('/login')
134
+ break
135
+ case 'API_ERROR':
136
+ showNotification(`API Error: ${error.message}`)
137
+ break
138
+ case 'NETWORK_ERROR':
139
+ showNotification('Network unavailable')
140
+ break
141
+ }
142
+ return
143
+ }
144
+
145
+ // Safe to use data here
146
+ console.log(data)
147
+ ```
148
+
149
+ ## Configuration
150
+
151
+ ### Environment Variables
152
+
153
+ Copy `.env.example` to `.env` and configure:
154
+
155
+ ```env
156
+ # EEN OAuth Client ID (required for authentication)
157
+ VITE_EEN_CLIENT_ID=your-een-client-id
158
+
159
+ # Test credentials for Playwright E2E tests
160
+ TEST_USER=your-test-email@example.com
161
+ TEST_PASSWORD=your-test-password
162
+
163
+ # OAuth proxy URL (required for API calls)
164
+ # Use local proxy for development to avoid Cloudflare rate limits
165
+ VITE_PROXY_URL=http://localhost:8787
166
+
167
+ # npm access token for publishing (Automation type from npmjs.com)
168
+ NPM_TOKEN=npm_xxxxxxxxxxxx
169
+
170
+ # Slack webhook for notifications
171
+ SLACK_WEBHOOK=https://hooks.slack.com/services/xxx/xxx/xxx
172
+
173
+ # Anthropic API key for Claude code review
174
+ ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxx
175
+ ```
176
+
177
+ ### GitHub Secrets
178
+
179
+ GitHub Actions workflows require these secrets. Sync them from your `.env` file:
180
+
181
+ ```bash
182
+ # Preview what will be synced
183
+ ./scripts/sync-secrets.sh --dry-run
184
+
185
+ # Sync secrets to GitHub
186
+ ./scripts/sync-secrets.sh
187
+ ```
188
+
189
+ Required secrets:
190
+ | Secret | Purpose |
191
+ |--------|---------|
192
+ | `VITE_EEN_CLIENT_ID` | EEN OAuth client ID |
193
+ | `TEST_USER` | E2E test user email |
194
+ | `TEST_PASSWORD` | E2E test user password |
195
+ | `VITE_PROXY_URL` | OAuth proxy URL |
196
+ | `NPM_TOKEN` | npm publish token |
197
+ | `SLACK_WEBHOOK` | Slack notifications |
198
+ | `ANTHROPIC_API_KEY` | Claude code review |
199
+
200
+ ### Local Proxy Server
201
+
202
+ For development, run the local OAuth proxy to avoid Cloudflare rate limits:
203
+
204
+ ```bash
205
+ cd ../een-oauth-proxy/proxy
206
+ npm run dev
207
+ ```
208
+
209
+ This starts the proxy at `http://localhost:8787`. The proxy must be running for authentication and API calls to work.
210
+
211
+ ### Running E2E Tests
212
+
213
+ E2E tests use Playwright to authenticate with EEN and test actual API calls.
214
+
215
+ **Prerequisites:**
216
+ 1. Ensure the OAuth proxy is running:
217
+ ```bash
218
+ ./scripts/restart-proxy.sh
219
+ ```
220
+
221
+ 2. Set test credentials in `.env`:
222
+ ```env
223
+ TEST_USER=your-test-email@example.com
224
+ TEST_PASSWORD=your-test-password
225
+ VITE_EEN_CLIENT_ID=your-client-id
226
+ VITE_PROXY_URL=http://localhost:8787
227
+ ```
228
+
229
+ **Run tests:**
230
+ ```bash
231
+ npm run test:e2e # Run all E2E tests
232
+ npm run test:e2e:ui # Run with Playwright UI
233
+ ```
234
+
235
+ **Token caching:** Auth tokens are cached in `e2e/.auth-state.json` (with 5-minute expiry buffer) to speed up repeated test runs. Delete this file to force re-authentication.
236
+
237
+ **Security note:** The cached access token is stored in plaintext. This is acceptable for development/testing because:
238
+ - The token is short-lived (~1 hour)
239
+ - The file has restricted permissions (owner read/write only)
240
+ - The file is excluded from git
241
+
242
+ **Cleanup after tests:**
243
+ ```bash
244
+ ./scripts/cleanup-auth.sh # Revokes token and removes cache file
245
+ ```
246
+
247
+ ## Architecture
248
+
249
+ ```
250
+ ┌─────────────────────────────────────────────────────────┐
251
+ │ Developer's Vue 3 App │
252
+ │ ┌─────────────────────────────────────────────────┐ │
253
+ │ │ import from 'een-api-toolkit' │ │
254
+ │ │ ┌──────────────┐ ┌────────────────────┐ │ │
255
+ │ │ │ Composables │ │ Plain Functions │ │ │
256
+ │ │ │ useUsers() │ │ getUsers() │ │ │
257
+ │ │ │ useCameras() │ │ getCameras() │ │ │
258
+ │ │ └──────────────┘ └────────────────────┘ │ │
259
+ │ └─────────────────────────────────────────────────┘ │
260
+ │ │ │
261
+ │ ▼ │
262
+ │ ┌─────────────────────┐ │
263
+ │ │ Pinia Auth Store │ │
264
+ │ │ (token, baseUrl) │ │
265
+ │ └─────────────────────┘ │
266
+ └─────────────────────────────────────────────────────────┘
267
+
268
+
269
+ ┌─────────────────────┐
270
+ │ OAuth Proxy │
271
+ │ (Cloudflare Worker) │
272
+ └─────────────────────┘
273
+
274
+
275
+ ┌─────────────────────┐
276
+ │ EEN API v3.0 │
277
+ └─────────────────────┘
278
+ ```
279
+
280
+ ## Key Features
281
+
282
+ - **No direct API credentials** - OAuth handled via proxy
283
+ - **Reactive or plain** - Choose composables for Vue reactivity, plain functions for flexibility
284
+ - **Predictable errors** - Always `{data, error}`, no try/catch needed
285
+ - **Auto token refresh** - Handled internally by the toolkit
286
+ - **Type-safe** - Full TypeScript types from OpenAPI spec
287
+
288
+ ## Publishing (for maintainers)
289
+
290
+ The package is published to npm automatically when changes are merged to the `production` branch.
291
+
292
+ ### Version Management
293
+ - Patch version auto-increments on each commit (via Husky pre-commit hook)
294
+ - Minor/major versions updated manually in `package.json`
295
+
296
+ ### Release Flow
297
+ ```
298
+ feature branch → develop → production → npm publish
299
+ ```
300
+
301
+ ### Manual Publish
302
+ ```bash
303
+ npm login
304
+ npm run build
305
+ npm publish
306
+ ```
307
+
308
+ ## Documentation
309
+
310
+ - [AI-CONTEXT.md](./docs/AI-CONTEXT.md) - Comprehensive single-file reference for AI assistants
311
+ - [API Reference](./docs/api/) - Auto-generated TypeDoc documentation
312
+ - [Example App](./examples/vue-basic/) - Complete Vue 3 example application
313
+
314
+ ## API Reference
315
+
316
+ - [EEN API 3.0 Documentation](https://developer.eagleeyenetworks.com/reference/using-the-api)
317
+ - [EEN API 3.0 Spec](https://github.com/EENCloud/api-v3-documentation/tree/development/api/3.0)
318
+
319
+ ## License
320
+
321
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const ne=require("pinia"),l=require("vue"),f={};let p={};function oe(e={}){p={proxyUrl:e.proxyUrl??(f==null?void 0:f.VITE_PROXY_URL),clientId:e.clientId??(f==null?void 0:f.VITE_EEN_CLIENT_ID),redirectUri:e.redirectUri??(f==null?void 0:f.VITE_REDIRECT_URI),debug:e.debug??(f==null?void 0:f.VITE_DEBUG)==="true"}}function ae(){return p}function x(){return p.proxyUrl??(f==null?void 0:f.VITE_PROXY_URL)}function M(){return p.clientId??(f==null?void 0:f.VITE_EEN_CLIENT_ID)}function F(){return p.redirectUri??(f==null?void 0:f.VITE_REDIRECT_URI)??"http://127.0.0.1:3333"}function I(e){return{data:e,error:null}}function i(e,t,r,n){return{data:null,error:{code:e,message:t,status:r,details:n}}}const L={BASE_URL:"/",DEV:!1,MODE:"production",PROD:!0,SSR:!1},se=()=>{try{return(L==null?void 0:L.VITE_DEBUG)==="true"}catch{return!1}};function c(...e){se()&&console.log("[een-api-toolkit]",...e)}const R=ne.defineStore("een-auth",()=>{const e=l.ref(null),t=l.ref(null),r=l.ref(null),n=l.ref(null),o=l.ref(null),a=l.ref(443),u=l.ref(null),d=l.ref(null),E=l.ref(!1);let T=null;const v=l.ref(!1),g=l.ref(null),P=l.computed(()=>!!e.value),m=l.computed(()=>o.value?a.value===443?`https://${o.value}`:`https://${o.value}:${a.value}`:null),S=l.computed(()=>t.value?Date.now()>=t.value:!0),w=l.computed(()=>t.value?Math.max(0,t.value-Date.now()):0);function k(s,h){e.value=s,t.value=Date.now()+h*1e3,A(),O(),c("Token set, expires in",h,"seconds")}function D(s){r.value=s,A()}function U(s){n.value=s,A()}function W(s){if(typeof s=="string")try{const h=new URL(s.startsWith("http")?s:`https://${s}`);o.value=h.hostname,a.value=h.port?parseInt(h.port,10):443}catch(h){c("Failed to parse URL, using as hostname:",h instanceof Error?h.message:String(h)),o.value=s,a.value=443}else o.value=s.hostname,a.value=s.port??443;A(),c("Base URL set:",m.value)}function G(s){u.value=s,A()}function O(){if(d.value&&(clearTimeout(d.value),d.value=null),!t.value||!e.value)return;const s=Date.now(),y=t.value-s,_=5*60*1e3,ee=y/2,te=Math.min(_,ee),re=Math.max(y-te,60*1e3),b=Math.max(re,5e3);c("Auto-refresh scheduled in",Math.round(b/1e3),"seconds"),d.value=setTimeout(async()=>{await K()},b)}async function K(){return T?(c("Refresh already in progress, waiting for existing refresh"),T):(E.value=!0,c("Performing auto-refresh"),T=(async()=>{try{const{refreshToken:s}=await Promise.resolve().then(()=>ce),h=await s();h.error?(v.value=!0,g.value=h.error.message,c("Auto-refresh failed:",h.error.message)):(v.value=!1,g.value=null,c("Auto-refresh successful"))}catch(s){v.value=!0,g.value=s instanceof Error?s.message:String(s),c("Auto-refresh error:",s)}finally{E.value=!1,T=null}})(),T)}function X(){v.value=!1,g.value=null}function C(){d.value&&(clearTimeout(d.value),d.value=null),e.value=null,t.value=null,r.value=null,n.value=null,o.value=null,a.value=443,u.value=null,v.value=!1,g.value=null,Z(),c("Logged out")}function Y(){J(),e.value&&!S.value?(O(),c("Initialized from storage")):e.value&&S.value&&(c("Stored token expired, clearing"),C())}function A(){try{e.value&&localStorage.setItem("een_token",e.value),t.value&&localStorage.setItem("een_tokenExpiration",String(t.value)),r.value&&localStorage.setItem("een_refreshTokenMarker",r.value),n.value&&localStorage.setItem("een_sessionId",n.value),o.value&&localStorage.setItem("een_hostname",o.value),a.value!==443&&localStorage.setItem("een_port",String(a.value)),u.value&&localStorage.setItem("een_userProfile",JSON.stringify(u.value))}catch(s){c("Failed to save to localStorage:",s instanceof Error?s.message:String(s))}}function J(){try{e.value=localStorage.getItem("een_token");const s=localStorage.getItem("een_tokenExpiration");t.value=s?parseInt(s,10):null,r.value=localStorage.getItem("een_refreshTokenMarker"),n.value=localStorage.getItem("een_sessionId"),o.value=localStorage.getItem("een_hostname");const h=localStorage.getItem("een_port");a.value=h?parseInt(h,10):443;const y=localStorage.getItem("een_userProfile");u.value=y?JSON.parse(y):null}catch(s){c("Failed to load from localStorage:",s instanceof Error?s.message:String(s))}}function Z(){try{localStorage.removeItem("een_token"),localStorage.removeItem("een_tokenExpiration"),localStorage.removeItem("een_refreshTokenMarker"),localStorage.removeItem("een_sessionId"),localStorage.removeItem("een_hostname"),localStorage.removeItem("een_port"),localStorage.removeItem("een_userProfile")}catch(s){c("Failed to clear localStorage:",s instanceof Error?s.message:String(s))}}return{token:e,tokenExpiration:t,refreshTokenMarker:r,sessionId:n,hostname:o,port:a,userProfile:u,isRefreshing:E,refreshFailed:v,refreshFailedMessage:g,isAuthenticated:P,baseUrl:m,isTokenExpired:S,tokenExpiresIn:w,setToken:k,setRefreshTokenMarker:D,setSessionId:U,setBaseUrl:W,setUserProfile:G,setupAutoRefresh:O,clearRefreshFailed:X,logout:C,initialize:Y}}),ue="https://auth.eagleeyenetworks.com/oauth2/authorize";function j(){const e=M();if(!e)throw new Error("Client ID not configured. Call initEenToolkit() or set VITE_EEN_CLIENT_ID");const t=crypto.randomUUID();try{sessionStorage.setItem("een_oauth_state",t)}catch{}const r=new URLSearchParams({client_id:e,response_type:"code",scope:"vms.all",redirect_uri:F(),state:t});return c("Generated auth URL with state:",t),`${ue}?${r.toString()}`}async function N(e){const t=x();if(!t)return i("AUTH_FAILED","Proxy URL not configured. Call initEenToolkit() or set VITE_PROXY_URL");const r=new URLSearchParams({code:e,redirect_uri:F()});try{const n=await fetch(`${t}/proxy/getAccessToken?${r.toString()}`,{method:"POST",credentials:"include",headers:{Accept:"application/json"}});if(!n.ok){const a=await n.text().catch(()=>"Unknown error");return i("AUTH_FAILED",`Token exchange failed: ${a}`,n.status)}const o=await n.json();return c("Token received, expires in:",o.expiresIn),I(o)}catch(n){return i("NETWORK_ERROR",`Failed to exchange code: ${String(n)}`)}}async function B(){const e=x();if(!e)return i("AUTH_FAILED","Proxy URL not configured");const t=R();try{const r={Accept:"application/json"};t.sessionId&&(r.Authorization=`Bearer ${t.sessionId}`);const n=await fetch(`${e}/proxy/refreshAccessToken`,{method:"POST",credentials:"include",headers:r});if(!n.ok){const a=await n.text().catch(()=>"Unknown error");return i("AUTH_FAILED",`Token refresh failed: ${a}`,n.status)}const o=await n.json();return t.setToken(o.accessToken,o.expiresIn),c("Token refreshed, expires in:",o.expiresIn),I(o)}catch(r){return i("NETWORK_ERROR",`Failed to refresh token: ${String(r)}`)}}async function H(){const e=x();if(!e)return i("AUTH_FAILED","Proxy URL not configured");const t=R();try{const r={Accept:"application/json"};t.sessionId&&(r.Authorization=`Bearer ${t.sessionId}`);const n=await fetch(`${e}/proxy/revoke`,{method:"POST",credentials:"include",headers:r});if(t.logout(),!n.ok){const o=await n.text().catch(()=>"Unknown error");return i("AUTH_FAILED",`Token revocation failed: ${o}`,n.status)}return c("Token revoked"),I(void 0)}catch(r){return t.logout(),i("NETWORK_ERROR",`Failed to revoke token: ${String(r)}`)}}async function z(e,t){let r=null;try{r=sessionStorage.getItem("een_oauth_state"),sessionStorage.removeItem("een_oauth_state")}catch{}if(!r)return i("AUTH_FAILED","No OAuth state found. Please restart the login process.");if(!le(t,r))return i("AUTH_FAILED","Invalid OAuth state. Possible CSRF attack.");c("State validated, exchanging code for token");const n=await N(e);if(n.error)return n;const o=R(),a=n.data;return o.setToken(a.accessToken,a.expiresIn),o.setRefreshTokenMarker("present"),o.setSessionId(a.sessionId),o.setBaseUrl(a.httpsBaseUrl),c("Auth callback complete, user:",a.userEmail),I(a)}function le(e,t){if(e.length!==t.length)return!1;let r=0;for(let n=0;n<e.length;n++)r|=e.charCodeAt(n)^t.charCodeAt(n);return r===0}const ce=Object.freeze(Object.defineProperty({__proto__:null,getAccessToken:N,getAuthUrl:j,handleAuthCallback:z,refreshToken:B,revokeToken:H},Symbol.toStringTag,{value:"Module"}));async function V(){const e=R();if(!e.isAuthenticated)return i("AUTH_REQUIRED","Authentication required");if(!e.baseUrl)return i("AUTH_REQUIRED","Base URL not configured");const t=`${e.baseUrl}/api/v3.0/users/self`;c("Fetching current user:",t);try{const r=await fetch(t,{method:"GET",headers:{Accept:"application/json",Authorization:`Bearer ${e.token}`}});if(!r.ok)return $(r);const n=await r.json();return c("Current user fetched:",n.email),e.setUserProfile(n),I(n)}catch(r){return i("NETWORK_ERROR",`Failed to fetch current user: ${String(r)}`)}}async function q(e){var a;const t=R();if(!t.isAuthenticated)return i("AUTH_REQUIRED","Authentication required");if(!t.baseUrl)return i("AUTH_REQUIRED","Base URL not configured");const r=new URLSearchParams;e!=null&&e.pageSize&&r.append("pageSize",String(e.pageSize)),e!=null&&e.pageToken&&r.append("pageToken",e.pageToken),e!=null&&e.include&&e.include.length>0&&r.append("include",e.include.join(","));const n=r.toString(),o=`${t.baseUrl}/api/v3.0/users${n?`?${n}`:""}`;c("Fetching users:",o);try{const u=await fetch(o,{method:"GET",headers:{Accept:"application/json",Authorization:`Bearer ${t.token}`}});if(!u.ok)return $(u);const d=await u.json();return c("Users fetched:",((a=d.results)==null?void 0:a.length)??0,"users"),I(d)}catch(u){return i("NETWORK_ERROR",`Failed to fetch users: ${String(u)}`)}}async function Q(e,t){const r=R();if(!r.isAuthenticated)return i("AUTH_REQUIRED","Authentication required");if(!r.baseUrl)return i("AUTH_REQUIRED","Base URL not configured");if(!e)return i("VALIDATION_ERROR","User ID is required");const n=new URLSearchParams;t!=null&&t.include&&t.include.length>0&&n.append("include",t.include.join(","));const o=n.toString(),a=`${r.baseUrl}/api/v3.0/users/${encodeURIComponent(e)}${o?`?${o}`:""}`;c("Fetching user:",a);try{const u=await fetch(a,{method:"GET",headers:{Accept:"application/json",Authorization:`Bearer ${r.token}`}});if(!u.ok)return $(u);const d=await u.json();return c("User fetched:",d.email),I(d)}catch(u){return i("NETWORK_ERROR",`Failed to fetch user: ${String(u)}`)}}async function $(e){const t=e.status;let r;try{const n=await e.json();r=n.message??n.error??e.statusText}catch{r=e.statusText||"Unknown error"}switch(t){case 401:return i("AUTH_REQUIRED",`Authentication failed: ${r}`,t);case 403:return i("FORBIDDEN",`Access denied: ${r}`,t);case 404:return i("NOT_FOUND",`Not found: ${r}`,t);case 429:return i("RATE_LIMITED",`Rate limited: ${r}`,t);default:return i("API_ERROR",`API error: ${r}`,t)}}function ie(e){const t=l.ref(null),r=l.ref(!1),n=l.ref(null),o=async()=>{r.value=!0,n.value=null;const u=await V();return u.error?(n.value=u.error,t.value=null):t.value=u.data,r.value=!1,u},a=o;return(e==null?void 0:e.immediate)!==!1&&l.onMounted(o),{user:t,loading:r,error:n,fetch:o,refresh:a}}function fe(e,t){const r=l.ref([]),n=l.ref(!1),o=l.ref(null),a=l.ref(void 0),u=l.ref(void 0),d=l.ref(void 0),E=l.computed(()=>!!a.value),T=l.computed(()=>!!u.value),v=l.ref(e??{}),g=async k=>{n.value=!0,o.value=null;const D={...v.value,...k},U=await q(D);return U.error?(o.value=U.error,r.value=[],a.value=void 0,u.value=void 0,d.value=void 0):(r.value=U.data.results,a.value=U.data.nextPageToken,u.value=U.data.prevPageToken,d.value=U.data.totalSize),n.value=!1,U},P=()=>g(),m=async()=>{if(a.value)return g({...v.value,pageToken:a.value})},S=async()=>{if(u.value)return g({...v.value,pageToken:u.value})},w=k=>{v.value=k};return(t==null?void 0:t.immediate)!==!1&&l.onMounted(g),{users:r,loading:n,error:o,nextPageToken:a,prevPageToken:u,totalSize:d,hasNextPage:E,hasPrevPage:T,params:v,fetch:g,refresh:P,fetchNextPage:m,fetchPrevPage:S,setParams:w}}function de(e,t){const r=l.ref(null),n=l.ref(!1),o=l.ref(null),a=()=>typeof e=="function"?e():e,u=async E=>{const T=a();if(!T)return o.value={code:"VALIDATION_ERROR",message:"User ID is required"},{data:null,error:o.value};n.value=!0,o.value=null;const v={include:t==null?void 0:t.include,...E},g=await Q(T,v);return g.error?(o.value=g.error,r.value=null):r.value=g.data,n.value=!1,g},d=()=>u();return(t==null?void 0:t.immediate)!==!1&&a()&&l.onMounted(u),{user:r,loading:n,error:o,fetch:u,refresh:d}}exports.failure=i;exports.getAccessToken=N;exports.getAuthUrl=j;exports.getClientId=M;exports.getConfig=ae;exports.getCurrentUser=V;exports.getProxyUrl=x;exports.getRedirectUri=F;exports.getUser=Q;exports.getUsers=q;exports.handleAuthCallback=z;exports.initEenToolkit=oe;exports.refreshToken=B;exports.revokeToken=H;exports.success=I;exports.useAuthStore=R;exports.useCurrentUser=ie;exports.useUser=de;exports.useUsers=fe;
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","sources":["../src/config.ts","../src/types/common.ts","../src/utils/debug.ts","../src/auth/store.ts","../src/auth/service.ts","../src/users/service.ts","../src/users/composables.ts"],"sourcesContent":["import type { EenToolkitConfig } from './types'\n\n/**\n * Global toolkit configuration\n */\nlet config: EenToolkitConfig = {}\n\n/**\n * Initialize the EEN API Toolkit\n */\nexport function initEenToolkit(options: EenToolkitConfig = {}): void {\n config = {\n proxyUrl: options.proxyUrl ?? import.meta.env?.VITE_PROXY_URL,\n clientId: options.clientId ?? import.meta.env?.VITE_EEN_CLIENT_ID,\n redirectUri: options.redirectUri ?? import.meta.env?.VITE_REDIRECT_URI,\n debug: options.debug ?? import.meta.env?.VITE_DEBUG === 'true'\n }\n}\n\n/**\n * Get the current configuration\n */\nexport function getConfig(): EenToolkitConfig {\n return config\n}\n\n/**\n * Get the proxy URL\n */\nexport function getProxyUrl(): string | undefined {\n return config.proxyUrl ?? import.meta.env?.VITE_PROXY_URL\n}\n\n/**\n * Get the client ID\n */\nexport function getClientId(): string | undefined {\n return config.clientId ?? import.meta.env?.VITE_EEN_CLIENT_ID\n}\n\n/**\n * Get the redirect URI\n */\nexport function getRedirectUri(): string {\n return config.redirectUri ?? import.meta.env?.VITE_REDIRECT_URI ?? 'http://127.0.0.1:3333'\n}\n","/**\n * Error codes returned by the toolkit.\n *\n * @remarks\n * All API functions return a {@link Result} type that contains either data or an error.\n * The error code helps you determine how to handle the failure.\n *\n * @category Types\n */\nexport type ErrorCode =\n | 'AUTH_REQUIRED'\n | 'AUTH_FAILED'\n | 'TOKEN_EXPIRED'\n | 'API_ERROR'\n | 'NETWORK_ERROR'\n | 'VALIDATION_ERROR'\n | 'NOT_FOUND'\n | 'FORBIDDEN'\n | 'RATE_LIMITED'\n | 'UNKNOWN_ERROR'\n\n/**\n * Error object returned when an operation fails.\n *\n * @remarks\n * Contains structured error information including a machine-readable code,\n * human-readable message, and optional HTTP status code.\n *\n * @example\n * ```typescript\n * const { error } = await getUsers()\n * if (error) {\n * console.error(`${error.code}: ${error.message}`)\n * if (error.status === 401) {\n * redirectToLogin()\n * }\n * }\n * ```\n *\n * @category Types\n */\nexport interface EenError {\n /** Machine-readable error code for programmatic handling */\n code: ErrorCode\n /** Human-readable error message */\n message: string\n /** HTTP status code if the error came from an API response */\n status?: number\n /** Additional error details (varies by error type) */\n details?: unknown\n}\n\n/**\n * Result type for all API operations - functions never throw exceptions.\n *\n * @remarks\n * This is a discriminated union type. When `error` is `null`, `data` contains\n * the successful result. When `error` is not `null`, `data` is `null`.\n * TypeScript will narrow the type correctly after checking for errors.\n *\n * @example\n * ```typescript\n * const { data, error } = await getUsers()\n *\n * if (error) {\n * // TypeScript knows: data is null, error is EenError\n * console.error(error.message)\n * return\n * }\n *\n * // TypeScript knows: data is not null, error is null\n * console.log(data.results)\n * ```\n *\n * @typeParam T - The type of the data on success\n * @category Types\n */\nexport type Result<T> =\n | { data: T; error: null }\n | { data: null; error: EenError }\n\n/**\n * Pagination parameters for list operations.\n *\n * @remarks\n * Most list APIs in the EEN platform support pagination. Use `pageSize` to\n * control how many results are returned, and `pageToken` to fetch subsequent pages.\n *\n * @example\n * ```typescript\n * // First page\n * const { data } = await getUsers({ pageSize: 50 })\n *\n * // Next page (if available)\n * if (data.nextPageToken) {\n * const { data: page2 } = await getUsers({\n * pageSize: 50,\n * pageToken: data.nextPageToken\n * })\n * }\n * ```\n *\n * @category Types\n */\nexport interface PaginationParams {\n /** Number of results per page (default varies by endpoint, typically 100) */\n pageSize?: number\n /** Token for fetching a specific page (from previous response's nextPageToken) */\n pageToken?: string\n}\n\n/**\n * Paginated response from list operations.\n *\n * @remarks\n * Contains the results array and optional pagination tokens for navigating\n * through large result sets.\n *\n * @typeParam T - The type of items in the results array\n * @category Types\n */\nexport interface PaginatedResult<T> {\n /** Array of items for this page */\n results: T[]\n /** Token to fetch the next page (undefined if no more pages) */\n nextPageToken?: string\n /** Token to fetch the previous page (undefined if on first page) */\n prevPageToken?: string\n /** Total number of items across all pages (may not be provided by all endpoints) */\n totalSize?: number\n}\n\n/**\n * Configuration for initializing the toolkit.\n *\n * @remarks\n * Pass this to {@link initEenToolkit} to configure the library. All options\n * can also be set via environment variables (VITE_PROXY_URL, VITE_EEN_CLIENT_ID,\n * VITE_REDIRECT_URI, VITE_DEBUG).\n *\n * @example\n * ```typescript\n * import { initEenToolkit } from 'een-api-toolkit'\n *\n * initEenToolkit({\n * proxyUrl: 'https://your-proxy.workers.dev',\n * clientId: 'your-een-client-id',\n * redirectUri: 'http://localhost:5173/callback',\n * debug: true\n * })\n * ```\n *\n * @category Configuration\n */\nexport interface EenToolkitConfig {\n /** URL of the OAuth proxy server (required for API calls) */\n proxyUrl?: string\n /** EEN OAuth client ID (required for authentication) */\n clientId?: string\n /** OAuth redirect URI (default: http://127.0.0.1:3333) */\n redirectUri?: string\n /** Enable debug logging to console */\n debug?: boolean\n}\n\n/**\n * Helper to create a success result.\n *\n * @param data - The successful result data\n * @returns A Result object with data and null error\n *\n * @internal\n */\nexport function success<T>(data: T): Result<T> {\n return { data, error: null }\n}\n\n/**\n * Helper to create an error result.\n *\n * @param code - The error code\n * @param message - Human-readable error message\n * @param status - Optional HTTP status code\n * @param details - Optional additional error details\n * @returns A Result object with null data and error\n *\n * @internal\n */\nexport function failure<T>(code: ErrorCode, message: string, status?: number, details?: unknown): Result<T> {\n return { data: null, error: { code, message, status, details } }\n}\n","/**\n * Debug logging utility\n * Enabled when VITE_DEBUG=true in environment\n */\n\nconst isDebugEnabled = (): boolean => {\n try {\n return import.meta.env?.VITE_DEBUG === 'true'\n } catch {\n return false\n }\n}\n\nexport function debug(...args: unknown[]): void {\n if (isDebugEnabled()) {\n console.log('[een-api-toolkit]', ...args)\n }\n}\n\nexport function debugWarn(...args: unknown[]): void {\n if (isDebugEnabled()) {\n console.warn('[een-api-toolkit]', ...args)\n }\n}\n\nexport function debugError(...args: unknown[]): void {\n if (isDebugEnabled()) {\n console.error('[een-api-toolkit]', ...args)\n }\n}\n","import { defineStore } from 'pinia'\nimport { ref, computed } from 'vue'\nimport type { UserProfile } from '../types'\nimport { debug } from '../utils/debug'\n\n/**\n * Pinia store for authentication state management\n */\nexport const useAuthStore = defineStore('een-auth', () => {\n // State\n const token = ref<string | null>(null)\n const tokenExpiration = ref<number | null>(null)\n const refreshTokenMarker = ref<string | null>(null)\n const sessionId = ref<string | null>(null)\n const hostname = ref<string | null>(null)\n const port = ref<number>(443)\n const userProfile = ref<UserProfile | null>(null)\n const refreshTimerId = ref<ReturnType<typeof setTimeout> | null>(null)\n const isRefreshing = ref(false)\n let refreshPromise: Promise<void> | null = null\n const refreshFailed = ref(false)\n const refreshFailedMessage = ref<string | null>(null)\n\n // Computed\n const isAuthenticated = computed(() => !!token.value)\n\n const baseUrl = computed(() => {\n if (!hostname.value) return null\n return port.value === 443\n ? `https://${hostname.value}`\n : `https://${hostname.value}:${port.value}`\n })\n\n const isTokenExpired = computed(() => {\n if (!tokenExpiration.value) return true\n return Date.now() >= tokenExpiration.value\n })\n\n const tokenExpiresIn = computed(() => {\n if (!tokenExpiration.value) return 0\n return Math.max(0, tokenExpiration.value - Date.now())\n })\n\n // Actions\n function setToken(newToken: string, expiresIn: number) {\n token.value = newToken\n tokenExpiration.value = Date.now() + expiresIn * 1000\n saveToStorage()\n setupAutoRefresh()\n debug('Token set, expires in', expiresIn, 'seconds')\n }\n\n function setRefreshTokenMarker(marker: string) {\n refreshTokenMarker.value = marker\n saveToStorage()\n }\n\n function setSessionId(newSessionId: string) {\n sessionId.value = newSessionId\n saveToStorage()\n }\n\n function setBaseUrl(data: string | { hostname: string; port?: number }) {\n if (typeof data === 'string') {\n // Parse URL string\n try {\n const url = new URL(data.startsWith('http') ? data : `https://${data}`)\n hostname.value = url.hostname\n port.value = url.port ? parseInt(url.port, 10) : 443\n } catch (err: unknown) {\n // Invalid URL format, use as hostname directly\n debug('Failed to parse URL, using as hostname:', err instanceof Error ? err.message : String(err))\n hostname.value = data\n port.value = 443\n }\n } else {\n hostname.value = data.hostname\n port.value = data.port ?? 443\n }\n saveToStorage()\n debug('Base URL set:', baseUrl.value)\n }\n\n function setUserProfile(profile: UserProfile) {\n userProfile.value = profile\n saveToStorage()\n }\n\n function setupAutoRefresh() {\n // Clear existing timer\n if (refreshTimerId.value) {\n clearTimeout(refreshTimerId.value)\n refreshTimerId.value = null\n }\n\n if (!tokenExpiration.value || !token.value) {\n return\n }\n\n const now = Date.now()\n const expiresAt = tokenExpiration.value\n const timeUntilExpiry = expiresAt - now\n\n // Calculate refresh time: 5 minutes before expiration or 50% of TTL, whichever is earlier\n // Minimum: 1 minute before expiration, minimum timeout: 5 seconds\n const fiveMinutes = 5 * 60 * 1000\n const halfTtl = timeUntilExpiry / 2\n const refreshBuffer = Math.min(fiveMinutes, halfTtl)\n const refreshTime = Math.max(timeUntilExpiry - refreshBuffer, 60 * 1000)\n const timeout = Math.max(refreshTime, 5000)\n\n debug('Auto-refresh scheduled in', Math.round(timeout / 1000), 'seconds')\n\n refreshTimerId.value = setTimeout(async () => {\n await performAutoRefresh()\n }, timeout)\n }\n\n async function performAutoRefresh(): Promise<void> {\n // If refresh is already in progress, wait for the existing promise\n if (refreshPromise) {\n debug('Refresh already in progress, waiting for existing refresh')\n return refreshPromise\n }\n\n isRefreshing.value = true\n debug('Performing auto-refresh')\n\n refreshPromise = (async () => {\n try {\n // Import dynamically to avoid circular dependency\n const { refreshToken } = await import('./service')\n const result = await refreshToken()\n\n if (result.error) {\n refreshFailed.value = true\n refreshFailedMessage.value = result.error.message\n debug('Auto-refresh failed:', result.error.message)\n } else {\n refreshFailed.value = false\n refreshFailedMessage.value = null\n debug('Auto-refresh successful')\n }\n } catch (err: unknown) {\n refreshFailed.value = true\n refreshFailedMessage.value = err instanceof Error ? err.message : String(err)\n debug('Auto-refresh error:', err)\n } finally {\n isRefreshing.value = false\n refreshPromise = null\n }\n })()\n\n return refreshPromise\n }\n\n function clearRefreshFailed() {\n refreshFailed.value = false\n refreshFailedMessage.value = null\n }\n\n function logout() {\n // Clear timer\n if (refreshTimerId.value) {\n clearTimeout(refreshTimerId.value)\n refreshTimerId.value = null\n }\n\n // Clear state\n token.value = null\n tokenExpiration.value = null\n refreshTokenMarker.value = null\n sessionId.value = null\n hostname.value = null\n port.value = 443\n userProfile.value = null\n refreshFailed.value = false\n refreshFailedMessage.value = null\n\n // Clear storage\n clearStorage()\n debug('Logged out')\n }\n\n function initialize() {\n loadFromStorage()\n if (token.value && !isTokenExpired.value) {\n setupAutoRefresh()\n debug('Initialized from storage')\n } else if (token.value && isTokenExpired.value) {\n debug('Stored token expired, clearing')\n logout()\n }\n }\n\n // Storage helpers\n function saveToStorage() {\n try {\n if (token.value) localStorage.setItem('een_token', token.value)\n if (tokenExpiration.value) localStorage.setItem('een_tokenExpiration', String(tokenExpiration.value))\n if (refreshTokenMarker.value) localStorage.setItem('een_refreshTokenMarker', refreshTokenMarker.value)\n if (sessionId.value) localStorage.setItem('een_sessionId', sessionId.value)\n if (hostname.value) localStorage.setItem('een_hostname', hostname.value)\n if (port.value !== 443) localStorage.setItem('een_port', String(port.value))\n if (userProfile.value) localStorage.setItem('een_userProfile', JSON.stringify(userProfile.value))\n } catch (err: unknown) {\n // localStorage might not be available (SSR, private browsing, etc.)\n debug('Failed to save to localStorage:', err instanceof Error ? err.message : String(err))\n }\n }\n\n function loadFromStorage() {\n try {\n token.value = localStorage.getItem('een_token')\n const expStr = localStorage.getItem('een_tokenExpiration')\n tokenExpiration.value = expStr ? parseInt(expStr, 10) : null\n refreshTokenMarker.value = localStorage.getItem('een_refreshTokenMarker')\n sessionId.value = localStorage.getItem('een_sessionId')\n hostname.value = localStorage.getItem('een_hostname')\n const portStr = localStorage.getItem('een_port')\n port.value = portStr ? parseInt(portStr, 10) : 443\n const profileStr = localStorage.getItem('een_userProfile')\n userProfile.value = profileStr ? JSON.parse(profileStr) : null\n } catch (err: unknown) {\n // localStorage might not be available (SSR, private browsing, etc.)\n debug('Failed to load from localStorage:', err instanceof Error ? err.message : String(err))\n }\n }\n\n function clearStorage() {\n try {\n localStorage.removeItem('een_token')\n localStorage.removeItem('een_tokenExpiration')\n localStorage.removeItem('een_refreshTokenMarker')\n localStorage.removeItem('een_sessionId')\n localStorage.removeItem('een_hostname')\n localStorage.removeItem('een_port')\n localStorage.removeItem('een_userProfile')\n } catch (err: unknown) {\n // localStorage might not be available (SSR, private browsing, etc.)\n debug('Failed to clear localStorage:', err instanceof Error ? err.message : String(err))\n }\n }\n\n return {\n // State\n token,\n tokenExpiration,\n refreshTokenMarker,\n sessionId,\n hostname,\n port,\n userProfile,\n isRefreshing,\n refreshFailed,\n refreshFailedMessage,\n\n // Computed\n isAuthenticated,\n baseUrl,\n isTokenExpired,\n tokenExpiresIn,\n\n // Actions\n setToken,\n setRefreshTokenMarker,\n setSessionId,\n setBaseUrl,\n setUserProfile,\n setupAutoRefresh,\n clearRefreshFailed,\n logout,\n initialize\n }\n})\n","import { useAuthStore } from './store'\nimport { getProxyUrl, getClientId, getRedirectUri } from '../config'\nimport { success, failure } from '../types'\nimport type { Result } from '../types'\nimport { debug } from '../utils/debug'\n\nconst EEN_AUTH_URL = 'https://auth.eagleeyenetworks.com/oauth2/authorize'\n\n/**\n * Token response from the OAuth proxy.\n *\n * @remarks\n * This is the response returned by the proxy's `/proxy/getAccessToken` endpoint\n * after successfully exchanging an authorization code for an access token.\n *\n * @category Authentication\n */\nexport interface TokenResponse {\n accessToken: string\n expiresIn: number\n httpsBaseUrl: string | { hostname: string; port?: number }\n userEmail: string\n sessionId: string\n}\n\n/**\n * Generate the OAuth authorization URL\n */\nexport function getAuthUrl(): string {\n const clientId = getClientId()\n if (!clientId) {\n throw new Error('Client ID not configured. Call initEenToolkit() or set VITE_EEN_CLIENT_ID')\n }\n\n // Generate and store state for CSRF protection\n const state = crypto.randomUUID()\n try {\n sessionStorage.setItem('een_oauth_state', state)\n } catch {\n // sessionStorage might not be available\n }\n\n const params = new URLSearchParams({\n client_id: clientId,\n response_type: 'code',\n scope: 'vms.all',\n redirect_uri: getRedirectUri(),\n state\n })\n\n debug('Generated auth URL with state:', state)\n return `${EEN_AUTH_URL}?${params.toString()}`\n}\n\n/**\n * Exchange authorization code for access token\n */\nexport async function getAccessToken(code: string): Promise<Result<TokenResponse>> {\n const proxyUrl = getProxyUrl()\n if (!proxyUrl) {\n return failure('AUTH_FAILED', 'Proxy URL not configured. Call initEenToolkit() or set VITE_PROXY_URL')\n }\n\n const params = new URLSearchParams({\n code,\n redirect_uri: getRedirectUri()\n })\n\n try {\n const response = await fetch(`${proxyUrl}/proxy/getAccessToken?${params.toString()}`, {\n method: 'POST',\n credentials: 'include',\n headers: {\n 'Accept': 'application/json'\n }\n })\n\n if (!response.ok) {\n const errorText = await response.text().catch(() => 'Unknown error')\n return failure('AUTH_FAILED', `Token exchange failed: ${errorText}`, response.status)\n }\n\n const data = await response.json() as TokenResponse\n debug('Token received, expires in:', data.expiresIn)\n return success(data)\n } catch (err) {\n return failure('NETWORK_ERROR', `Failed to exchange code: ${String(err)}`)\n }\n}\n\n/**\n * Refresh the access token using stored refresh token\n */\nexport async function refreshToken(): Promise<Result<{ accessToken: string; expiresIn: number }>> {\n const proxyUrl = getProxyUrl()\n if (!proxyUrl) {\n return failure('AUTH_FAILED', 'Proxy URL not configured')\n }\n\n const authStore = useAuthStore()\n\n try {\n const headers: HeadersInit = {\n 'Accept': 'application/json'\n }\n\n // Add session ID header as fallback for environments where cookies don't work\n if (authStore.sessionId) {\n headers['Authorization'] = `Bearer ${authStore.sessionId}`\n }\n\n const response = await fetch(`${proxyUrl}/proxy/refreshAccessToken`, {\n method: 'POST',\n credentials: 'include',\n headers\n })\n\n if (!response.ok) {\n const errorText = await response.text().catch(() => 'Unknown error')\n return failure('AUTH_FAILED', `Token refresh failed: ${errorText}`, response.status)\n }\n\n const data = await response.json() as { accessToken: string; expiresIn: number }\n\n // Update store with new token\n authStore.setToken(data.accessToken, data.expiresIn)\n\n debug('Token refreshed, expires in:', data.expiresIn)\n return success(data)\n } catch (err) {\n return failure('NETWORK_ERROR', `Failed to refresh token: ${String(err)}`)\n }\n}\n\n/**\n * Revoke the current token and logout\n */\nexport async function revokeToken(): Promise<Result<void>> {\n const proxyUrl = getProxyUrl()\n if (!proxyUrl) {\n return failure('AUTH_FAILED', 'Proxy URL not configured')\n }\n\n const authStore = useAuthStore()\n\n try {\n const headers: HeadersInit = {\n 'Accept': 'application/json'\n }\n\n if (authStore.sessionId) {\n headers['Authorization'] = `Bearer ${authStore.sessionId}`\n }\n\n const response = await fetch(`${proxyUrl}/proxy/revoke`, {\n method: 'POST',\n credentials: 'include',\n headers\n })\n\n // Logout regardless of response\n authStore.logout()\n\n if (!response.ok) {\n const errorText = await response.text().catch(() => 'Unknown error')\n return failure('AUTH_FAILED', `Token revocation failed: ${errorText}`, response.status)\n }\n\n debug('Token revoked')\n return success(undefined)\n } catch (err) {\n // Still logout on error\n authStore.logout()\n return failure('NETWORK_ERROR', `Failed to revoke token: ${String(err)}`)\n }\n}\n\n/**\n * Handle OAuth callback - validates state and exchanges code for token\n */\nexport async function handleAuthCallback(code: string, state: string): Promise<Result<TokenResponse>> {\n // Validate state for CSRF protection\n let storedState: string | null = null\n try {\n storedState = sessionStorage.getItem('een_oauth_state')\n sessionStorage.removeItem('een_oauth_state')\n } catch {\n // sessionStorage might not be available\n }\n\n if (!storedState) {\n return failure('AUTH_FAILED', 'No OAuth state found. Please restart the login process.')\n }\n\n // Constant-time comparison to prevent timing attacks\n if (!constantTimeEquals(state, storedState)) {\n return failure('AUTH_FAILED', 'Invalid OAuth state. Possible CSRF attack.')\n }\n\n debug('State validated, exchanging code for token')\n\n // Exchange code for token\n const result = await getAccessToken(code)\n\n if (result.error) {\n return result\n }\n\n // Update auth store with received data\n const authStore = useAuthStore()\n const data = result.data\n\n authStore.setToken(data.accessToken, data.expiresIn)\n authStore.setRefreshTokenMarker('present')\n authStore.setSessionId(data.sessionId)\n authStore.setBaseUrl(data.httpsBaseUrl)\n\n debug('Auth callback complete, user:', data.userEmail)\n\n return success(data)\n}\n\n/**\n * Constant-time string comparison to prevent timing attacks\n */\nfunction constantTimeEquals(a: string, b: string): boolean {\n if (a.length !== b.length) {\n return false\n }\n\n let result = 0\n for (let i = 0; i < a.length; i++) {\n result |= a.charCodeAt(i) ^ b.charCodeAt(i)\n }\n\n return result === 0\n}\n","import { useAuthStore } from '../auth/store'\nimport { success, failure } from '../types'\nimport type { Result, PaginatedResult, User, UserProfile, ListUsersParams, GetUserParams } from '../types'\nimport { debug } from '../utils/debug'\n\n/**\n * Get the current authenticated user's profile.\n *\n * @remarks\n * Fetches the profile of the currently authenticated user from `/api/v3.0/users/self`.\n * The result is also stored in the auth store for easy access via `useAuthStore().userProfile`.\n *\n * @returns A Result containing the user profile or an error\n *\n * @example\n * ```typescript\n * import { getCurrentUser } from 'een-api-toolkit'\n *\n * const { data, error } = await getCurrentUser()\n *\n * if (error) {\n * if (error.code === 'AUTH_REQUIRED') {\n * router.push('/login')\n * }\n * return\n * }\n *\n * console.log(`Welcome, ${data.firstName} ${data.lastName}`)\n * ```\n *\n * @category Users\n */\nexport async function getCurrentUser(): Promise<Result<UserProfile>> {\n const authStore = useAuthStore()\n\n if (!authStore.isAuthenticated) {\n return failure('AUTH_REQUIRED', 'Authentication required')\n }\n\n if (!authStore.baseUrl) {\n return failure('AUTH_REQUIRED', 'Base URL not configured')\n }\n\n const url = `${authStore.baseUrl}/api/v3.0/users/self`\n debug('Fetching current user:', url)\n\n try {\n const response = await fetch(url, {\n method: 'GET',\n headers: {\n 'Accept': 'application/json',\n 'Authorization': `Bearer ${authStore.token}`\n }\n })\n\n if (!response.ok) {\n return handleErrorResponse(response)\n }\n\n const data = await response.json() as UserProfile\n debug('Current user fetched:', data.email)\n\n // Update profile in store\n authStore.setUserProfile(data)\n\n return success(data)\n } catch (err) {\n return failure('NETWORK_ERROR', `Failed to fetch current user: ${String(err)}`)\n }\n}\n\n/**\n * List users with optional pagination and filtering.\n *\n * @remarks\n * Fetches a paginated list of users from `/api/v3.0/users`. Use the `pageSize`\n * parameter to control how many results are returned per page, and `pageToken`\n * to navigate to subsequent pages.\n *\n * For more details, see the\n * [EEN API Documentation](https://developer.eagleeyenetworks.com/reference/listusers).\n *\n * @param params - Optional pagination and filtering parameters\n * @returns A Result containing a paginated list of users or an error\n *\n * @example\n * ```typescript\n * import { getUsers } from 'een-api-toolkit'\n *\n * // Basic usage\n * const { data, error } = await getUsers()\n * if (data) {\n * console.log(`Found ${data.results.length} users`)\n * }\n *\n * // With pagination\n * const { data } = await getUsers({ pageSize: 50 })\n * if (data?.nextPageToken) {\n * const { data: page2 } = await getUsers({\n * pageSize: 50,\n * pageToken: data.nextPageToken\n * })\n * }\n *\n * // Fetch all users\n * let allUsers: User[] = []\n * let pageToken: string | undefined\n * do {\n * const { data, error } = await getUsers({ pageSize: 100, pageToken })\n * if (error) break\n * allUsers.push(...data.results)\n * pageToken = data.nextPageToken\n * } while (pageToken)\n * ```\n *\n * @category Users\n */\nexport async function getUsers(params?: ListUsersParams): Promise<Result<PaginatedResult<User>>> {\n const authStore = useAuthStore()\n\n if (!authStore.isAuthenticated) {\n return failure('AUTH_REQUIRED', 'Authentication required')\n }\n\n if (!authStore.baseUrl) {\n return failure('AUTH_REQUIRED', 'Base URL not configured')\n }\n\n const queryParams = new URLSearchParams()\n\n if (params?.pageSize) {\n queryParams.append('pageSize', String(params.pageSize))\n }\n if (params?.pageToken) {\n queryParams.append('pageToken', params.pageToken)\n }\n if (params?.include && params.include.length > 0) {\n queryParams.append('include', params.include.join(','))\n }\n\n const queryString = queryParams.toString()\n const url = `${authStore.baseUrl}/api/v3.0/users${queryString ? `?${queryString}` : ''}`\n debug('Fetching users:', url)\n\n try {\n const response = await fetch(url, {\n method: 'GET',\n headers: {\n 'Accept': 'application/json',\n 'Authorization': `Bearer ${authStore.token}`\n }\n })\n\n if (!response.ok) {\n return handleErrorResponse(response)\n }\n\n const data = await response.json() as PaginatedResult<User>\n debug('Users fetched:', data.results?.length ?? 0, 'users')\n\n return success(data)\n } catch (err) {\n return failure('NETWORK_ERROR', `Failed to fetch users: ${String(err)}`)\n }\n}\n\n/**\n * Get a specific user by ID.\n *\n * @remarks\n * Fetches a single user from `/api/v3.0/users/{userId}`. Use the `include`\n * parameter to request additional fields like permissions.\n *\n * For more details, see the\n * [EEN API Documentation](https://developer.eagleeyenetworks.com/reference/getuser).\n *\n * @param userId - The unique identifier of the user to fetch\n * @param params - Optional parameters (e.g., include additional fields)\n * @returns A Result containing the user or an error\n *\n * @example\n * ```typescript\n * import { getUser } from 'een-api-toolkit'\n *\n * const { data, error } = await getUser('user-123')\n *\n * if (error) {\n * if (error.code === 'NOT_FOUND') {\n * console.log('User not found')\n * }\n * return\n * }\n *\n * console.log(`User: ${data.firstName} ${data.lastName}`)\n *\n * // With permissions\n * const { data: userWithPerms } = await getUser('user-123', {\n * include: ['permissions']\n * })\n * console.log('Permissions:', userWithPerms?.permissions)\n * ```\n *\n * @category Users\n */\nexport async function getUser(userId: string, params?: GetUserParams): Promise<Result<User>> {\n const authStore = useAuthStore()\n\n if (!authStore.isAuthenticated) {\n return failure('AUTH_REQUIRED', 'Authentication required')\n }\n\n if (!authStore.baseUrl) {\n return failure('AUTH_REQUIRED', 'Base URL not configured')\n }\n\n if (!userId) {\n return failure('VALIDATION_ERROR', 'User ID is required')\n }\n\n const queryParams = new URLSearchParams()\n\n if (params?.include && params.include.length > 0) {\n queryParams.append('include', params.include.join(','))\n }\n\n const queryString = queryParams.toString()\n const url = `${authStore.baseUrl}/api/v3.0/users/${encodeURIComponent(userId)}${queryString ? `?${queryString}` : ''}`\n debug('Fetching user:', url)\n\n try {\n const response = await fetch(url, {\n method: 'GET',\n headers: {\n 'Accept': 'application/json',\n 'Authorization': `Bearer ${authStore.token}`\n }\n })\n\n if (!response.ok) {\n return handleErrorResponse(response)\n }\n\n const data = await response.json() as User\n debug('User fetched:', data.email)\n\n return success(data)\n } catch (err) {\n return failure('NETWORK_ERROR', `Failed to fetch user: ${String(err)}`)\n }\n}\n\n/**\n * Handle error responses from the API.\n * @internal\n */\nasync function handleErrorResponse<T>(response: Response): Promise<Result<T>> {\n const status = response.status\n\n let message: string\n try {\n const errorData = await response.json()\n message = errorData.message ?? errorData.error ?? response.statusText\n } catch {\n message = response.statusText || 'Unknown error'\n }\n\n switch (status) {\n case 401:\n return failure('AUTH_REQUIRED', `Authentication failed: ${message}`, status)\n case 403:\n return failure('FORBIDDEN', `Access denied: ${message}`, status)\n case 404:\n return failure('NOT_FOUND', `Not found: ${message}`, status)\n case 429:\n return failure('RATE_LIMITED', `Rate limited: ${message}`, status)\n default:\n return failure('API_ERROR', `API error: ${message}`, status)\n }\n}\n","import { ref, computed, onMounted } from 'vue'\nimport { getCurrentUser, getUsers, getUser } from './service'\nimport type { User, UserProfile, EenError, ListUsersParams, GetUserParams } from '../types'\n\n/**\n * Options for the useCurrentUser composable.\n *\n * @category Users\n */\nexport interface UseCurrentUserOptions {\n /**\n * Whether to fetch the user immediately on mount.\n * @defaultValue true\n */\n immediate?: boolean\n}\n\n/**\n * Vue 3 composable for getting the current authenticated user.\n *\n * @remarks\n * Provides reactive access to the current user's profile with automatic\n * fetching on component mount (configurable via options).\n *\n * @param options - Configuration options\n * @returns Reactive user state and control functions\n *\n * @example\n * ```vue\n * <script setup>\n * import { useCurrentUser } from 'een-api-toolkit'\n *\n * const { user, loading, error, refresh } = useCurrentUser()\n * </script>\n *\n * <template>\n * <div v-if=\"loading\">Loading...</div>\n * <div v-else-if=\"error\">Error: {{ error.message }}</div>\n * <div v-else-if=\"user\">\n * <h1>Welcome, {{ user.firstName }}!</h1>\n * <p>Email: {{ user.email }}</p>\n * <button @click=\"refresh\">Refresh</button>\n * </div>\n * </template>\n * ```\n *\n * @example\n * ```typescript\n * // Manual fetch (don't fetch on mount)\n * const { user, fetch } = useCurrentUser({ immediate: false })\n *\n * onMounted(async () => {\n * if (someCondition) {\n * await fetch()\n * }\n * })\n * ```\n *\n * @category Users\n */\nexport function useCurrentUser(options?: UseCurrentUserOptions) {\n /** The current user profile, or null if not loaded */\n const user = ref<UserProfile | null>(null)\n /** Whether a fetch is in progress */\n const loading = ref(false)\n /** The last error that occurred, or null if successful */\n const error = ref<EenError | null>(null)\n\n const fetch = async () => {\n loading.value = true\n error.value = null\n\n const result = await getCurrentUser()\n\n if (result.error) {\n error.value = result.error\n user.value = null\n } else {\n user.value = result.data\n }\n\n loading.value = false\n return result\n }\n\n /** Alias for fetch - refresh the current user data */\n const refresh = fetch\n\n // Fetch immediately by default\n if (options?.immediate !== false) {\n onMounted(fetch)\n }\n\n return {\n user,\n loading,\n error,\n fetch,\n refresh\n }\n}\n\n/**\n * Options for the useUsers composable.\n *\n * @category Users\n */\nexport interface UseUsersOptions {\n /**\n * Whether to fetch users immediately on mount.\n * @defaultValue true\n */\n immediate?: boolean\n}\n\n/**\n * Vue 3 composable for listing users with pagination.\n *\n * @remarks\n * Provides reactive access to a paginated list of users with built-in\n * pagination controls. Automatically fetches on mount unless disabled.\n *\n * @param initialParams - Initial pagination/filter parameters\n * @param options - Configuration options\n * @returns Reactive users state and pagination controls\n *\n * @example\n * ```vue\n * <script setup>\n * import { useUsers } from 'een-api-toolkit'\n *\n * const {\n * users,\n * loading,\n * error,\n * hasNextPage,\n * fetchNextPage,\n * refresh\n * } = useUsers({ pageSize: 20 })\n * </script>\n *\n * <template>\n * <div v-if=\"loading\">Loading...</div>\n * <div v-else-if=\"error\">Error: {{ error.message }}</div>\n * <div v-else>\n * <ul>\n * <li v-for=\"user in users\" :key=\"user.id\">\n * {{ user.firstName }} {{ user.lastName }} ({{ user.email }})\n * </li>\n * </ul>\n * <button v-if=\"hasNextPage\" @click=\"fetchNextPage\">\n * Load More\n * </button>\n * <button @click=\"refresh\">Refresh</button>\n * </div>\n * </template>\n * ```\n *\n * @example\n * ```typescript\n * // Change parameters dynamically\n * const { users, setParams, fetch } = useUsers()\n *\n * async function searchUsers(query: string) {\n * setParams({ pageSize: 50 })\n * await fetch()\n * }\n * ```\n *\n * @category Users\n */\nexport function useUsers(initialParams?: ListUsersParams, options?: UseUsersOptions) {\n /** Array of users for the current page */\n const users = ref<User[]>([])\n /** Whether a fetch is in progress */\n const loading = ref(false)\n /** The last error that occurred, or null if successful */\n const error = ref<EenError | null>(null)\n /** Token for fetching the next page */\n const nextPageToken = ref<string | undefined>(undefined)\n /** Token for fetching the previous page */\n const prevPageToken = ref<string | undefined>(undefined)\n /** Total number of users (if provided by API) */\n const totalSize = ref<number | undefined>(undefined)\n\n /** Whether there is a next page available */\n const hasNextPage = computed(() => !!nextPageToken.value)\n /** Whether there is a previous page available */\n const hasPrevPage = computed(() => !!prevPageToken.value)\n\n /** Current pagination/filter parameters */\n const params = ref<ListUsersParams>(initialParams ?? {})\n\n const fetch = async (fetchParams?: ListUsersParams) => {\n loading.value = true\n error.value = null\n\n const mergedParams = { ...params.value, ...fetchParams }\n const result = await getUsers(mergedParams)\n\n if (result.error) {\n error.value = result.error\n users.value = []\n nextPageToken.value = undefined\n prevPageToken.value = undefined\n totalSize.value = undefined\n } else {\n users.value = result.data.results\n nextPageToken.value = result.data.nextPageToken\n prevPageToken.value = result.data.prevPageToken\n totalSize.value = result.data.totalSize\n }\n\n loading.value = false\n return result\n }\n\n /** Refresh the current page */\n const refresh = () => fetch()\n\n /** Fetch the next page of results */\n const fetchNextPage = async () => {\n if (!nextPageToken.value) return\n return fetch({ ...params.value, pageToken: nextPageToken.value })\n }\n\n /** Fetch the previous page of results */\n const fetchPrevPage = async () => {\n if (!prevPageToken.value) return\n return fetch({ ...params.value, pageToken: prevPageToken.value })\n }\n\n /** Update the pagination/filter parameters */\n const setParams = (newParams: ListUsersParams) => {\n params.value = newParams\n }\n\n // Fetch immediately by default\n if (options?.immediate !== false) {\n onMounted(fetch)\n }\n\n return {\n users,\n loading,\n error,\n nextPageToken,\n prevPageToken,\n totalSize,\n hasNextPage,\n hasPrevPage,\n params,\n fetch,\n refresh,\n fetchNextPage,\n fetchPrevPage,\n setParams\n }\n}\n\n/**\n * Options for the useUser composable.\n *\n * @category Users\n */\nexport interface UseUserOptions {\n /**\n * Whether to fetch the user immediately on mount.\n * @defaultValue true\n */\n immediate?: boolean\n /**\n * Additional fields to include in the response.\n */\n include?: string[]\n}\n\n/**\n * Vue 3 composable for getting a single user by ID.\n *\n * @remarks\n * Provides reactive access to a specific user. The user ID can be provided\n * as a string or a getter function (useful for reactive route params).\n *\n * @param userId - The user ID (string or getter function)\n * @param options - Configuration options\n * @returns Reactive user state and control functions\n *\n * @example\n * ```vue\n * <script setup>\n * import { useUser } from 'een-api-toolkit'\n * import { useRoute } from 'vue-router'\n *\n * const route = useRoute()\n *\n * // Static ID\n * const { user, loading, error } = useUser('user-123')\n *\n * // Or reactive ID from route\n * const { user: routeUser } = useUser(() => route.params.id as string)\n * </script>\n *\n * <template>\n * <div v-if=\"loading\">Loading...</div>\n * <div v-else-if=\"error\">Error: {{ error.message }}</div>\n * <div v-else-if=\"user\">\n * <h1>{{ user.firstName }} {{ user.lastName }}</h1>\n * <p>Email: {{ user.email }}</p>\n * </div>\n * </template>\n * ```\n *\n * @example\n * ```typescript\n * // With additional fields\n * const { user } = useUser('user-123', {\n * include: ['permissions']\n * })\n *\n * // Access permissions when loaded\n * watchEffect(() => {\n * if (user.value?.permissions) {\n * console.log('User permissions:', user.value.permissions)\n * }\n * })\n * ```\n *\n * @category Users\n */\nexport function useUser(userId: string | (() => string), options?: UseUserOptions) {\n /** The user, or null if not loaded */\n const user = ref<User | null>(null)\n /** Whether a fetch is in progress */\n const loading = ref(false)\n /** The last error that occurred, or null if successful */\n const error = ref<EenError | null>(null)\n\n const resolveUserId = () => {\n return typeof userId === 'function' ? userId() : userId\n }\n\n const fetch = async (params?: GetUserParams) => {\n const id = resolveUserId()\n if (!id) {\n error.value = { code: 'VALIDATION_ERROR', message: 'User ID is required' }\n return { data: null, error: error.value }\n }\n\n loading.value = true\n error.value = null\n\n const mergedParams: GetUserParams = {\n include: options?.include,\n ...params\n }\n\n const result = await getUser(id, mergedParams)\n\n if (result.error) {\n error.value = result.error\n user.value = null\n } else {\n user.value = result.data\n }\n\n loading.value = false\n return result\n }\n\n /** Refresh the user data */\n const refresh = () => fetch()\n\n // Fetch immediately by default if userId is provided\n if (options?.immediate !== false && resolveUserId()) {\n onMounted(fetch)\n }\n\n return {\n user,\n loading,\n error,\n fetch,\n refresh\n }\n}\n"],"names":["config","initEenToolkit","options","__vite_import_meta_env__","getConfig","getProxyUrl","getClientId","getRedirectUri","success","data","failure","code","message","status","details","isDebugEnabled","debug","args","useAuthStore","defineStore","token","ref","tokenExpiration","refreshTokenMarker","sessionId","hostname","port","userProfile","refreshTimerId","isRefreshing","refreshPromise","refreshFailed","refreshFailedMessage","isAuthenticated","computed","baseUrl","isTokenExpired","tokenExpiresIn","setToken","newToken","expiresIn","saveToStorage","setupAutoRefresh","setRefreshTokenMarker","marker","setSessionId","newSessionId","setBaseUrl","url","err","setUserProfile","profile","now","timeUntilExpiry","fiveMinutes","halfTtl","refreshBuffer","refreshTime","timeout","performAutoRefresh","refreshToken","service","result","clearRefreshFailed","logout","clearStorage","initialize","loadFromStorage","expStr","portStr","profileStr","EEN_AUTH_URL","getAuthUrl","clientId","state","params","getAccessToken","proxyUrl","response","errorText","authStore","headers","revokeToken","handleAuthCallback","storedState","constantTimeEquals","a","b","i","getCurrentUser","handleErrorResponse","getUsers","queryParams","queryString","_a","getUser","userId","errorData","useCurrentUser","user","loading","error","fetch","refresh","onMounted","useUsers","initialParams","users","nextPageToken","prevPageToken","totalSize","hasNextPage","hasPrevPage","fetchParams","mergedParams","fetchNextPage","fetchPrevPage","setParams","newParams","useUser","resolveUserId","id"],"mappings":"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"}