een-api-toolkit 0.0.18 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/CHANGELOG.md +8 -5
  2. package/README.md +29 -41
  3. package/dist/index.cjs +1 -1
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.ts +339 -821
  6. package/dist/index.js +295 -340
  7. package/dist/index.js.map +1 -1
  8. package/docs/AI-CONTEXT.md +151 -237
  9. package/examples/vue-bridges/.env.example +13 -0
  10. package/examples/vue-bridges/e2e/app.spec.ts +73 -0
  11. package/examples/vue-bridges/e2e/auth.spec.ts +206 -0
  12. package/examples/{vue-basic → vue-bridges}/index.html +1 -1
  13. package/examples/vue-bridges/package-lock.json +1583 -0
  14. package/examples/vue-bridges/package.json +28 -0
  15. package/examples/vue-bridges/playwright.config.ts +46 -0
  16. package/examples/vue-bridges/src/App.vue +108 -0
  17. package/examples/vue-bridges/src/router/index.ts +68 -0
  18. package/examples/vue-bridges/src/views/BridgeDetail.vue +279 -0
  19. package/examples/vue-bridges/src/views/Bridges.vue +297 -0
  20. package/examples/vue-bridges/src/views/Callback.vue +76 -0
  21. package/examples/vue-bridges/src/views/Home.vue +150 -0
  22. package/examples/vue-bridges/src/views/Login.vue +33 -0
  23. package/examples/vue-bridges/src/views/Logout.vue +66 -0
  24. package/examples/vue-bridges/src/vite-env.d.ts +12 -0
  25. package/examples/vue-cameras/e2e/app.spec.ts +2 -2
  26. package/examples/vue-cameras/e2e/auth.spec.ts +206 -0
  27. package/examples/vue-cameras/src/App.vue +4 -4
  28. package/examples/vue-cameras/src/views/CameraDetail.vue +57 -9
  29. package/examples/vue-cameras/src/views/Cameras.vue +69 -18
  30. package/examples/vue-cameras/src/views/Home.vue +36 -11
  31. package/examples/{vue-basic → vue-users}/README.md +4 -4
  32. package/examples/{vue-basic → vue-users}/e2e/app.spec.ts +3 -3
  33. package/examples/{vue-basic → vue-users}/e2e/auth.spec.ts +2 -2
  34. package/examples/vue-users/index.html +13 -0
  35. package/examples/{vue-basic → vue-users}/package-lock.json +3 -3
  36. package/examples/{vue-basic → vue-users}/package.json +1 -1
  37. package/examples/{vue-basic → vue-users}/src/App.vue +1 -1
  38. package/examples/vue-users/src/main.ts +23 -0
  39. package/examples/{vue-basic → vue-users}/src/views/Home.vue +27 -12
  40. package/examples/{vue-basic → vue-users}/src/views/Users.vue +51 -10
  41. package/examples/vue-users/tsconfig.json +21 -0
  42. package/examples/vue-users/tsconfig.node.json +10 -0
  43. package/examples/vue-users/vite.config.ts +12 -0
  44. package/package.json +1 -1
  45. /package/examples/{vue-basic → vue-bridges}/src/main.ts +0 -0
  46. /package/examples/{vue-basic → vue-bridges}/tsconfig.json +0 -0
  47. /package/examples/{vue-basic → vue-bridges}/tsconfig.node.json +0 -0
  48. /package/examples/{vue-basic → vue-bridges}/vite.config.ts +0 -0
  49. /package/examples/{vue-basic → vue-users}/.env.example +0 -0
  50. /package/examples/{vue-basic → vue-users}/playwright.config.ts +0 -0
  51. /package/examples/{vue-basic → vue-users}/src/router/index.ts +0 -0
  52. /package/examples/{vue-basic → vue-users}/src/views/Callback.vue +0 -0
  53. /package/examples/{vue-basic → vue-users}/src/views/Login.vue +0 -0
  54. /package/examples/{vue-basic → vue-users}/src/views/Logout.vue +0 -0
  55. /package/examples/{vue-basic → vue-users}/src/vite-env.d.ts +0 -0
@@ -0,0 +1,297 @@
1
+ <script setup lang="ts">
2
+ import { ref, computed, watch, onMounted } from 'vue'
3
+ import { getBridges, type Bridge, type BridgeStatus, type EenError, type ListBridgesParams } from 'een-api-toolkit'
4
+
5
+ // Status filter
6
+ const statusFilter = ref<BridgeStatus | ''>('')
7
+
8
+ // Reactive state
9
+ const bridges = ref<Bridge[]>([])
10
+ const loading = ref(false)
11
+ const error = ref<EenError | null>(null)
12
+ const nextPageToken = ref<string | undefined>(undefined)
13
+ const totalSize = ref<number | undefined>(undefined)
14
+
15
+ const hasNextPage = computed(() => !!nextPageToken.value)
16
+
17
+ const params = ref<ListBridgesParams>({
18
+ pageSize: 20,
19
+ include: ['deviceInfo', 'status', 'networkInfo']
20
+ })
21
+
22
+ async function fetchBridges(fetchParams?: ListBridgesParams, append = false) {
23
+ loading.value = true
24
+ error.value = null
25
+
26
+ const mergedParams = { ...params.value, ...fetchParams }
27
+ const result = await getBridges(mergedParams)
28
+
29
+ if (result.error) {
30
+ error.value = result.error
31
+ if (!append) {
32
+ bridges.value = []
33
+ totalSize.value = undefined
34
+ }
35
+ nextPageToken.value = undefined
36
+ } else {
37
+ if (append) {
38
+ bridges.value = [...bridges.value, ...result.data.results]
39
+ } else {
40
+ bridges.value = result.data.results
41
+ }
42
+ nextPageToken.value = result.data.nextPageToken
43
+ totalSize.value = result.data.totalSize
44
+ }
45
+
46
+ loading.value = false
47
+ return result
48
+ }
49
+
50
+ function refresh() {
51
+ return fetchBridges()
52
+ }
53
+
54
+ async function fetchNextPage() {
55
+ if (!nextPageToken.value) return
56
+ return fetchBridges({ ...params.value, pageToken: nextPageToken.value }, true)
57
+ }
58
+
59
+ function setParams(newParams: ListBridgesParams) {
60
+ params.value = newParams
61
+ }
62
+
63
+ // Watch for status filter changes
64
+ watch(statusFilter, async (newStatus) => {
65
+ if (newStatus) {
66
+ setParams({
67
+ pageSize: 20,
68
+ include: ['deviceInfo', 'status', 'networkInfo'],
69
+ status__in: [newStatus]
70
+ })
71
+ } else {
72
+ setParams({
73
+ pageSize: 20,
74
+ include: ['deviceInfo', 'status', 'networkInfo']
75
+ })
76
+ }
77
+ await fetchBridges()
78
+ })
79
+
80
+ // Helper to extract status string from the union type
81
+ function getStatusString(status?: BridgeStatus | { connectionStatus?: BridgeStatus }): BridgeStatus | undefined {
82
+ if (!status) return undefined
83
+ if (typeof status === 'string') return status
84
+ return status.connectionStatus
85
+ }
86
+
87
+ // Get status badge class
88
+ function getStatusClass(status?: BridgeStatus | { connectionStatus?: BridgeStatus }): string {
89
+ const statusStr = getStatusString(status)
90
+ switch (statusStr) {
91
+ case 'online':
92
+ return 'status-online'
93
+ case 'offline':
94
+ return 'status-offline'
95
+ case 'error':
96
+ return 'status-error'
97
+ default:
98
+ return 'status-unknown'
99
+ }
100
+ }
101
+
102
+ onMounted(() => {
103
+ fetchBridges()
104
+ })
105
+ </script>
106
+
107
+ <template>
108
+ <div class="bridges">
109
+ <div class="header">
110
+ <h2>Bridges</h2>
111
+ <div class="controls">
112
+ <select v-model="statusFilter" class="status-filter">
113
+ <option value="">All Statuses</option>
114
+ <option value="online">Online</option>
115
+ <option value="offline">Offline</option>
116
+ <option value="error">Error</option>
117
+ <option value="idle">Idle</option>
118
+ </select>
119
+ <button @click="refresh" :disabled="loading">
120
+ {{ loading ? 'Loading...' : 'Refresh' }}
121
+ </button>
122
+ </div>
123
+ </div>
124
+
125
+ <div v-if="totalSize !== undefined" class="total-count">
126
+ Total: {{ totalSize }} bridge{{ totalSize !== 1 ? 's' : '' }}
127
+ </div>
128
+
129
+ <div v-if="loading && bridges.length === 0" class="loading">
130
+ Loading bridges...
131
+ </div>
132
+
133
+ <div v-else-if="error" class="error">
134
+ Error: {{ error.message }}
135
+ </div>
136
+
137
+ <div v-else>
138
+ <div v-if="bridges.length > 0" class="bridge-grid">
139
+ <router-link
140
+ v-for="bridge in bridges"
141
+ :key="bridge.id"
142
+ :to="`/bridges/${bridge.id}`"
143
+ class="bridge-card"
144
+ >
145
+ <div class="bridge-header">
146
+ <h3>{{ bridge.name }}</h3>
147
+ <span :class="['status-badge', getStatusClass(bridge.status)]">
148
+ {{ getStatusString(bridge.status) || 'Unknown' }}
149
+ </span>
150
+ </div>
151
+ <div class="bridge-details">
152
+ <p v-if="bridge.deviceInfo?.make || bridge.deviceInfo?.model">
153
+ <strong>Device:</strong>
154
+ {{ bridge.deviceInfo?.make || '' }} {{ bridge.deviceInfo?.model || '' }}
155
+ </p>
156
+ <p v-if="bridge.networkInfo?.localIpAddress">
157
+ <strong>IP:</strong> {{ bridge.networkInfo.localIpAddress }}
158
+ </p>
159
+ <p v-if="bridge.locationId">
160
+ <strong>Location:</strong> {{ bridge.locationId }}
161
+ </p>
162
+ <p v-if="bridge.tags && bridge.tags.length > 0">
163
+ <strong>Tags:</strong> {{ bridge.tags.join(', ') }}
164
+ </p>
165
+ </div>
166
+ </router-link>
167
+ </div>
168
+
169
+ <p v-else class="no-bridges">
170
+ No bridges found{{ statusFilter ? ' with selected filter' : '' }}.
171
+ </p>
172
+
173
+ <div v-if="hasNextPage" class="pagination">
174
+ <button @click="fetchNextPage" :disabled="loading">
175
+ {{ loading ? 'Loading...' : 'Load More' }}
176
+ </button>
177
+ </div>
178
+ </div>
179
+ </div>
180
+ </template>
181
+
182
+ <style scoped>
183
+ .bridges {
184
+ max-width: 1000px;
185
+ margin: 0 auto;
186
+ }
187
+
188
+ .header {
189
+ display: flex;
190
+ justify-content: space-between;
191
+ align-items: center;
192
+ margin-bottom: 10px;
193
+ }
194
+
195
+ .controls {
196
+ display: flex;
197
+ gap: 10px;
198
+ align-items: center;
199
+ }
200
+
201
+ .status-filter {
202
+ padding: 10px;
203
+ border: 1px solid #ddd;
204
+ border-radius: 4px;
205
+ font-size: 1rem;
206
+ }
207
+
208
+ .total-count {
209
+ color: #666;
210
+ margin-bottom: 20px;
211
+ }
212
+
213
+ .bridge-grid {
214
+ display: grid;
215
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
216
+ gap: 20px;
217
+ }
218
+
219
+ .bridge-card {
220
+ background: #fff;
221
+ border: 1px solid #eee;
222
+ border-radius: 8px;
223
+ padding: 20px;
224
+ text-decoration: none;
225
+ color: inherit;
226
+ transition: box-shadow 0.2s, border-color 0.2s;
227
+ }
228
+
229
+ .bridge-card:hover {
230
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
231
+ border-color: #42b883;
232
+ }
233
+
234
+ .bridge-header {
235
+ display: flex;
236
+ justify-content: space-between;
237
+ align-items: flex-start;
238
+ margin-bottom: 15px;
239
+ }
240
+
241
+ .bridge-header h3 {
242
+ margin: 0;
243
+ font-size: 1.1rem;
244
+ flex: 1;
245
+ margin-right: 10px;
246
+ }
247
+
248
+ .status-badge {
249
+ padding: 4px 8px;
250
+ border-radius: 4px;
251
+ font-size: 0.75rem;
252
+ font-weight: 600;
253
+ text-transform: uppercase;
254
+ white-space: nowrap;
255
+ }
256
+
257
+ .status-online {
258
+ background: #d4edda;
259
+ color: #155724;
260
+ }
261
+
262
+ .status-offline {
263
+ background: #f8d7da;
264
+ color: #721c24;
265
+ }
266
+
267
+ .status-error {
268
+ background: #fff3cd;
269
+ color: #856404;
270
+ }
271
+
272
+ .status-unknown {
273
+ background: #e2e3e5;
274
+ color: #383d41;
275
+ }
276
+
277
+ .bridge-details p {
278
+ margin: 5px 0;
279
+ font-size: 0.9rem;
280
+ color: #666;
281
+ }
282
+
283
+ .bridge-details strong {
284
+ color: #333;
285
+ }
286
+
287
+ .no-bridges {
288
+ text-align: center;
289
+ color: #666;
290
+ padding: 40px;
291
+ }
292
+
293
+ .pagination {
294
+ margin-top: 30px;
295
+ text-align: center;
296
+ }
297
+ </style>
@@ -0,0 +1,76 @@
1
+ <script setup lang="ts">
2
+ import { onMounted, ref } from 'vue'
3
+ import { useRouter } from 'vue-router'
4
+ import { handleAuthCallback } from 'een-api-toolkit'
5
+
6
+ const router = useRouter()
7
+ const error = ref<string | null>(null)
8
+ const processing = ref(true)
9
+
10
+ onMounted(async () => {
11
+ const url = new URL(window.location.href)
12
+ const code = url.searchParams.get('code')
13
+ const state = url.searchParams.get('state')
14
+ const errorParam = url.searchParams.get('error')
15
+
16
+ if (errorParam) {
17
+ error.value = `OAuth error: ${errorParam}`
18
+ processing.value = false
19
+ return
20
+ }
21
+
22
+ if (!code || !state) {
23
+ error.value = 'Missing authorization code or state parameter'
24
+ processing.value = false
25
+ return
26
+ }
27
+
28
+ const result = await handleAuthCallback(code, state)
29
+
30
+ if (result.error) {
31
+ error.value = result.error.message
32
+ processing.value = false
33
+ return
34
+ }
35
+
36
+ // Success - redirect to bridges
37
+ router.push('/bridges')
38
+ })
39
+ </script>
40
+
41
+ <template>
42
+ <div class="callback">
43
+ <div v-if="processing" class="loading">
44
+ <h2>Authenticating...</h2>
45
+ <p>Please wait while we complete the login process.</p>
46
+ </div>
47
+
48
+ <div v-else-if="error" class="error-state">
49
+ <h2>Authentication Failed</h2>
50
+ <p class="error">{{ error }}</p>
51
+ <router-link to="/login">
52
+ <button>Try Again</button>
53
+ </router-link>
54
+ </div>
55
+ </div>
56
+ </template>
57
+
58
+ <style scoped>
59
+ .callback {
60
+ text-align: center;
61
+ max-width: 400px;
62
+ margin: 0 auto;
63
+ }
64
+
65
+ h2 {
66
+ margin-bottom: 20px;
67
+ }
68
+
69
+ .loading p {
70
+ color: #666;
71
+ }
72
+
73
+ .error-state .error {
74
+ margin-bottom: 20px;
75
+ }
76
+ </style>
@@ -0,0 +1,150 @@
1
+ <script setup lang="ts">
2
+ import { useAuthStore, getCurrentUser, type UserProfile, type EenError } from 'een-api-toolkit'
3
+ import { computed, ref, onMounted } from 'vue'
4
+
5
+ const authStore = useAuthStore()
6
+ const isAuthenticated = computed(() => authStore.isAuthenticated)
7
+
8
+ // Reactive state for current user
9
+ const user = ref<UserProfile | null>(null)
10
+ const loading = ref(false)
11
+ const error = ref<EenError | null>(null)
12
+
13
+ async function fetchUser() {
14
+ if (!isAuthenticated.value) return
15
+
16
+ loading.value = true
17
+ error.value = null
18
+
19
+ const result = await getCurrentUser()
20
+ if (result.error) {
21
+ error.value = result.error
22
+ user.value = null
23
+ } else {
24
+ user.value = result.data
25
+ }
26
+
27
+ loading.value = false
28
+ }
29
+
30
+ onMounted(() => {
31
+ if (isAuthenticated.value) {
32
+ fetchUser()
33
+ }
34
+ })
35
+ </script>
36
+
37
+ <template>
38
+ <div class="home">
39
+ <h2>Welcome to the EEN Bridges Example</h2>
40
+
41
+ <div v-if="isAuthenticated" data-testid="authenticated">
42
+ <div v-if="loading" class="loading">Loading user info...</div>
43
+ <div v-else-if="error" class="error">{{ error.message }}</div>
44
+ <div v-else-if="user" class="user-info" data-testid="user-info">
45
+ <p>Logged in as: <strong>{{ user.firstName }} {{ user.lastName }}</strong></p>
46
+ <p>Email: {{ user.email }}</p>
47
+ </div>
48
+
49
+ <div class="actions">
50
+ <router-link to="/bridges">
51
+ <button data-testid="view-bridges-button">View Bridges</button>
52
+ </router-link>
53
+ </div>
54
+ </div>
55
+
56
+ <div v-else class="login-prompt" data-testid="not-authenticated">
57
+ <p>Please log in to view your bridges.</p>
58
+ <router-link to="/login">
59
+ <button data-testid="login-button">Login</button>
60
+ </router-link>
61
+ </div>
62
+
63
+ <div class="description">
64
+ <h3>About This Example</h3>
65
+ <p>
66
+ This example demonstrates how to use the <code>getBridges</code> and
67
+ <code>getBridge</code> functions from the EEN API Toolkit to display
68
+ and manage bridges from the Eagle Eye Networks platform.
69
+ </p>
70
+ <h4>Features</h4>
71
+ <ul>
72
+ <li>List bridges with pagination</li>
73
+ <li>Filter bridges by status</li>
74
+ <li>View bridge details</li>
75
+ <li>Display device and network information</li>
76
+ </ul>
77
+ </div>
78
+ </div>
79
+ </template>
80
+
81
+ <style scoped>
82
+ .home {
83
+ max-width: 600px;
84
+ margin: 0 auto;
85
+ }
86
+
87
+ h2 {
88
+ margin-bottom: 20px;
89
+ }
90
+
91
+ .user-info {
92
+ background: #f5f5f5;
93
+ padding: 15px;
94
+ border-radius: 4px;
95
+ margin-bottom: 20px;
96
+ }
97
+
98
+ .user-info p {
99
+ margin: 5px 0;
100
+ }
101
+
102
+ .actions {
103
+ margin: 20px 0;
104
+ }
105
+
106
+ .login-prompt {
107
+ text-align: center;
108
+ padding: 20px;
109
+ background: #f5f5f5;
110
+ border-radius: 4px;
111
+ margin-bottom: 20px;
112
+ }
113
+
114
+ .login-prompt p {
115
+ margin-bottom: 15px;
116
+ }
117
+
118
+ .description {
119
+ margin-top: 40px;
120
+ padding-top: 20px;
121
+ border-top: 1px solid #eee;
122
+ }
123
+
124
+ .description h3 {
125
+ margin-bottom: 10px;
126
+ }
127
+
128
+ .description h4 {
129
+ margin-top: 15px;
130
+ margin-bottom: 10px;
131
+ }
132
+
133
+ .description p {
134
+ color: #666;
135
+ margin-bottom: 15px;
136
+ }
137
+
138
+ .description ul {
139
+ list-style: disc;
140
+ padding-left: 20px;
141
+ color: #666;
142
+ }
143
+
144
+ .description code {
145
+ background: #f0f0f0;
146
+ padding: 2px 6px;
147
+ border-radius: 3px;
148
+ font-size: 0.9em;
149
+ }
150
+ </style>
@@ -0,0 +1,33 @@
1
+ <script setup lang="ts">
2
+ import { getAuthUrl } from 'een-api-toolkit'
3
+
4
+ function login() {
5
+ // Redirect to EEN OAuth login
6
+ window.location.href = getAuthUrl()
7
+ }
8
+ </script>
9
+
10
+ <template>
11
+ <div class="login">
12
+ <h2>Login</h2>
13
+ <p>Click the button below to login with your Eagle Eye Networks account.</p>
14
+ <button @click="login">Login with Eagle Eye Networks</button>
15
+ </div>
16
+ </template>
17
+
18
+ <style scoped>
19
+ .login {
20
+ text-align: center;
21
+ max-width: 400px;
22
+ margin: 0 auto;
23
+ }
24
+
25
+ h2 {
26
+ margin-bottom: 20px;
27
+ }
28
+
29
+ p {
30
+ margin-bottom: 20px;
31
+ color: #666;
32
+ }
33
+ </style>
@@ -0,0 +1,66 @@
1
+ <script setup lang="ts">
2
+ import { onMounted, ref } from 'vue'
3
+ import { useRouter } from 'vue-router'
4
+ import { revokeToken, useAuthStore } from 'een-api-toolkit'
5
+
6
+ const router = useRouter()
7
+ const authStore = useAuthStore()
8
+ const error = ref<string | null>(null)
9
+ const processing = ref(true)
10
+
11
+ onMounted(async () => {
12
+ if (!authStore.isAuthenticated) {
13
+ // Already logged out
14
+ router.push('/')
15
+ return
16
+ }
17
+
18
+ const result = await revokeToken()
19
+
20
+ if (result.error) {
21
+ error.value = result.error.message
22
+ processing.value = false
23
+ return
24
+ }
25
+
26
+ // Success - redirect to home
27
+ router.push('/')
28
+ })
29
+ </script>
30
+
31
+ <template>
32
+ <div class="logout">
33
+ <div v-if="processing" class="loading">
34
+ <h2>Logging out...</h2>
35
+ <p>Please wait while we complete the logout process.</p>
36
+ </div>
37
+
38
+ <div v-else-if="error" class="error-state">
39
+ <h2>Logout Failed</h2>
40
+ <p class="error">{{ error }}</p>
41
+ <router-link to="/">
42
+ <button>Return Home</button>
43
+ </router-link>
44
+ </div>
45
+ </div>
46
+ </template>
47
+
48
+ <style scoped>
49
+ .logout {
50
+ text-align: center;
51
+ max-width: 400px;
52
+ margin: 0 auto;
53
+ }
54
+
55
+ h2 {
56
+ margin-bottom: 20px;
57
+ }
58
+
59
+ .loading p {
60
+ color: #666;
61
+ }
62
+
63
+ .error-state .error {
64
+ margin-bottom: 20px;
65
+ }
66
+ </style>
@@ -0,0 +1,12 @@
1
+ /// <reference types="vite/client" />
2
+
3
+ interface ImportMetaEnv {
4
+ readonly VITE_PROXY_URL: string
5
+ readonly VITE_EEN_CLIENT_ID: string
6
+ readonly VITE_REDIRECT_URI?: string
7
+ readonly VITE_DEBUG?: string
8
+ }
9
+
10
+ interface ImportMeta {
11
+ readonly env: ImportMetaEnv
12
+ }
@@ -53,8 +53,8 @@ test.describe('Cameras Example App', () => {
53
53
 
54
54
  // Check the about section
55
55
  await expect(page.locator('.description h3')).toContainText('About This Example')
56
- await expect(page.locator('.description')).toContainText('useCameras')
57
- await expect(page.locator('.description')).toContainText('useCamera')
56
+ await expect(page.locator('.description')).toContainText('getCameras')
57
+ await expect(page.locator('.description')).toContainText('getCamera')
58
58
  })
59
59
 
60
60
  test('should have correct navigation structure', async ({ page }) => {