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,28 @@
1
+ {
2
+ "name": "een-api-toolkit-bridges-example",
3
+ "version": "0.0.1",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "stop": "npx kill-port 3333",
8
+ "dev": "npm run stop && vite",
9
+ "build": "vue-tsc && vite build",
10
+ "preview": "vite preview",
11
+ "test:e2e": "playwright test",
12
+ "test:e2e:ui": "playwright test --ui"
13
+ },
14
+ "dependencies": {
15
+ "een-api-toolkit": "file:../..",
16
+ "pinia": "^2.1.7",
17
+ "vue": "^3.4.0",
18
+ "vue-router": "^4.2.0"
19
+ },
20
+ "devDependencies": {
21
+ "@playwright/test": "^1.57.0",
22
+ "@vitejs/plugin-vue": "^6.0.0",
23
+ "dotenv": "^17.2.3",
24
+ "typescript": "~5.8.0",
25
+ "vite": "^7.3.0",
26
+ "vue-tsc": "^3.2.1"
27
+ }
28
+ }
@@ -0,0 +1,46 @@
1
+ import { defineConfig, devices } from '@playwright/test'
2
+ import dotenv from 'dotenv'
3
+ import path from 'path'
4
+ import { fileURLToPath } from 'url'
5
+
6
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
7
+
8
+ // Load .env files: parent first, then local with override to replace any conflicts
9
+ dotenv.config({ path: path.resolve(__dirname, '../../.env') })
10
+ dotenv.config({ path: path.resolve(__dirname, '.env'), override: true })
11
+
12
+ const redirectUri = process.env.VITE_REDIRECT_URI || 'http://127.0.0.1:3333'
13
+ if (!redirectUri.startsWith('http://127.0.0.1:') && !redirectUri.startsWith('http://localhost:')) {
14
+ throw new Error('VITE_REDIRECT_URI must use localhost or 127.0.0.1 for security')
15
+ }
16
+ const baseURL = redirectUri
17
+
18
+ export default defineConfig({
19
+ testDir: './e2e',
20
+ testMatch: '**/*.spec.ts',
21
+ fullyParallel: false,
22
+ forbidOnly: !!process.env.CI,
23
+ retries: 0,
24
+ maxFailures: 1,
25
+ workers: 1,
26
+ reporter: [['html', { open: 'never' }]],
27
+ timeout: 30000,
28
+ use: {
29
+ baseURL,
30
+ trace: 'on-first-retry',
31
+ video: 'retain-on-failure'
32
+ },
33
+ outputDir: './e2e-results/',
34
+ projects: [
35
+ {
36
+ name: 'chromium',
37
+ use: { ...devices['Desktop Chrome'] }
38
+ }
39
+ ],
40
+ webServer: {
41
+ command: 'npm run dev',
42
+ url: baseURL,
43
+ reuseExistingServer: !process.env.CI,
44
+ timeout: 30000
45
+ }
46
+ })
@@ -0,0 +1,108 @@
1
+ <script setup lang="ts">
2
+ import { useAuthStore } from 'een-api-toolkit'
3
+ import { computed } from 'vue'
4
+
5
+ const authStore = useAuthStore()
6
+ const isAuthenticated = computed(() => authStore.isAuthenticated)
7
+ </script>
8
+
9
+ <template>
10
+ <div class="app">
11
+ <header>
12
+ <h1>EEN Bridges Example</h1>
13
+ <nav>
14
+ <router-link to="/" data-testid="nav-home">Home</router-link>
15
+ <router-link v-if="isAuthenticated" to="/bridges" data-testid="nav-bridges">Bridges</router-link>
16
+ <router-link v-if="!isAuthenticated" to="/login" data-testid="nav-login">Login</router-link>
17
+ <router-link v-if="isAuthenticated" to="/logout" data-testid="nav-logout">Logout</router-link>
18
+ </nav>
19
+ </header>
20
+ <main>
21
+ <router-view />
22
+ </main>
23
+ </div>
24
+ </template>
25
+
26
+ <style>
27
+ * {
28
+ box-sizing: border-box;
29
+ margin: 0;
30
+ padding: 0;
31
+ }
32
+
33
+ body {
34
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
35
+ Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
36
+ line-height: 1.6;
37
+ color: #333;
38
+ }
39
+
40
+ .app {
41
+ max-width: 1200px;
42
+ margin: 0 auto;
43
+ padding: 20px;
44
+ }
45
+
46
+ header {
47
+ display: flex;
48
+ justify-content: space-between;
49
+ align-items: center;
50
+ margin-bottom: 30px;
51
+ padding-bottom: 20px;
52
+ border-bottom: 1px solid #eee;
53
+ }
54
+
55
+ header h1 {
56
+ font-size: 1.5rem;
57
+ }
58
+
59
+ nav {
60
+ display: flex;
61
+ gap: 20px;
62
+ }
63
+
64
+ nav a {
65
+ color: #42b883;
66
+ text-decoration: none;
67
+ }
68
+
69
+ nav a:hover {
70
+ text-decoration: underline;
71
+ }
72
+
73
+ nav a.router-link-active {
74
+ font-weight: bold;
75
+ }
76
+
77
+ button {
78
+ background: #42b883;
79
+ color: white;
80
+ border: none;
81
+ padding: 10px 20px;
82
+ border-radius: 4px;
83
+ cursor: pointer;
84
+ font-size: 1rem;
85
+ }
86
+
87
+ button:hover {
88
+ background: #3aa876;
89
+ }
90
+
91
+ button:disabled {
92
+ background: #ccc;
93
+ cursor: not-allowed;
94
+ }
95
+
96
+ .error {
97
+ color: #e74c3c;
98
+ padding: 10px;
99
+ background: #fdf2f2;
100
+ border-radius: 4px;
101
+ margin: 10px 0;
102
+ }
103
+
104
+ .loading {
105
+ color: #666;
106
+ font-style: italic;
107
+ }
108
+ </style>
@@ -0,0 +1,68 @@
1
+ import { createRouter, createWebHistory } from 'vue-router'
2
+ import { useAuthStore } from 'een-api-toolkit'
3
+ import Home from '../views/Home.vue'
4
+ import Login from '../views/Login.vue'
5
+ import Callback from '../views/Callback.vue'
6
+ import Bridges from '../views/Bridges.vue'
7
+ import BridgeDetail from '../views/BridgeDetail.vue'
8
+ import Logout from '../views/Logout.vue'
9
+
10
+ const router = createRouter({
11
+ history: createWebHistory(),
12
+ routes: [
13
+ {
14
+ path: '/',
15
+ name: 'home',
16
+ component: Home,
17
+ // Handle OAuth callback on root path (EEN IDP redirects to http://127.0.0.1:3333)
18
+ beforeEnter: (to, _from, next) => {
19
+ // If URL has code and state params, it's an OAuth callback
20
+ if (to.query.code && to.query.state) {
21
+ next({ name: 'callback', query: to.query })
22
+ } else {
23
+ next()
24
+ }
25
+ }
26
+ },
27
+ {
28
+ path: '/login',
29
+ name: 'login',
30
+ component: Login
31
+ },
32
+ {
33
+ path: '/callback',
34
+ name: 'callback',
35
+ component: Callback
36
+ },
37
+ {
38
+ path: '/bridges',
39
+ name: 'bridges',
40
+ component: Bridges,
41
+ meta: { requiresAuth: true }
42
+ },
43
+ {
44
+ path: '/bridges/:id',
45
+ name: 'bridge-detail',
46
+ component: BridgeDetail,
47
+ meta: { requiresAuth: true }
48
+ },
49
+ {
50
+ path: '/logout',
51
+ name: 'logout',
52
+ component: Logout
53
+ }
54
+ ]
55
+ })
56
+
57
+ // Navigation guard for protected routes
58
+ router.beforeEach((to, _from, next) => {
59
+ const authStore = useAuthStore()
60
+
61
+ if (to.meta.requiresAuth && !authStore.isAuthenticated) {
62
+ next({ name: 'login' })
63
+ } else {
64
+ next()
65
+ }
66
+ })
67
+
68
+ export default router
@@ -0,0 +1,279 @@
1
+ <script setup lang="ts">
2
+ import { ref, onMounted } from 'vue'
3
+ import { useRoute, useRouter } from 'vue-router'
4
+ import { getBridge, type Bridge, type BridgeStatus, type EenError } from 'een-api-toolkit'
5
+
6
+ const route = useRoute()
7
+ const router = useRouter()
8
+
9
+ const bridge = ref<Bridge | null>(null)
10
+ const loading = ref(false)
11
+ const error = ref<EenError | null>(null)
12
+
13
+ async function fetchBridge() {
14
+ const bridgeId = route.params.id as string
15
+ if (!bridgeId) {
16
+ error.value = { code: 'VALIDATION_ERROR', message: 'Bridge ID is required' }
17
+ return
18
+ }
19
+
20
+ loading.value = true
21
+ error.value = null
22
+
23
+ const result = await getBridge(bridgeId, {
24
+ include: ['deviceInfo', 'networkInfo', 'status', 'devicePosition']
25
+ })
26
+
27
+ if (result.error) {
28
+ error.value = result.error
29
+ bridge.value = null
30
+ } else {
31
+ bridge.value = result.data
32
+ }
33
+
34
+ loading.value = false
35
+ }
36
+
37
+ // Helper to extract status string from the union type
38
+ function getStatusString(status?: BridgeStatus | { connectionStatus?: BridgeStatus }): BridgeStatus | undefined {
39
+ if (!status) return undefined
40
+ if (typeof status === 'string') return status
41
+ return status.connectionStatus
42
+ }
43
+
44
+ // Get status badge class
45
+ function getStatusClass(status?: BridgeStatus | { connectionStatus?: BridgeStatus }): string {
46
+ const statusStr = getStatusString(status)
47
+ switch (statusStr) {
48
+ case 'online':
49
+ return 'status-online'
50
+ case 'offline':
51
+ return 'status-offline'
52
+ case 'error':
53
+ return 'status-error'
54
+ default:
55
+ return 'status-unknown'
56
+ }
57
+ }
58
+
59
+ function goBack() {
60
+ router.push('/bridges')
61
+ }
62
+
63
+ onMounted(() => {
64
+ fetchBridge()
65
+ })
66
+ </script>
67
+
68
+ <template>
69
+ <div class="bridge-detail">
70
+ <button @click="goBack" class="back-button">&larr; Back to Bridges</button>
71
+
72
+ <div v-if="loading" class="loading">
73
+ Loading bridge details...
74
+ </div>
75
+
76
+ <div v-else-if="error" class="error">
77
+ <h2>Error</h2>
78
+ <p>{{ error.message }}</p>
79
+ <button @click="goBack">Back to Bridges</button>
80
+ </div>
81
+
82
+ <div v-else-if="bridge" class="bridge-info">
83
+ <div class="bridge-header">
84
+ <h2>{{ bridge.name }}</h2>
85
+ <span :class="['status-badge', getStatusClass(bridge.status)]">
86
+ {{ getStatusString(bridge.status) || 'Unknown' }}
87
+ </span>
88
+ </div>
89
+
90
+ <div class="info-section">
91
+ <h3>Basic Information</h3>
92
+ <dl>
93
+ <dt>ID</dt>
94
+ <dd>{{ bridge.id }}</dd>
95
+
96
+ <dt>Account ID</dt>
97
+ <dd>{{ bridge.accountId }}</dd>
98
+
99
+ <dt v-if="bridge.locationId">Location ID</dt>
100
+ <dd v-if="bridge.locationId">{{ bridge.locationId }}</dd>
101
+
102
+ <dt v-if="bridge.timezone">Timezone</dt>
103
+ <dd v-if="bridge.timezone">{{ bridge.timezone }}</dd>
104
+
105
+ <dt v-if="bridge.tags?.length">Tags</dt>
106
+ <dd v-if="bridge.tags?.length">{{ bridge.tags.join(', ') }}</dd>
107
+ </dl>
108
+ </div>
109
+
110
+ <div v-if="bridge.deviceInfo" class="info-section">
111
+ <h3>Device Information</h3>
112
+ <dl>
113
+ <dt v-if="bridge.deviceInfo.make">Make</dt>
114
+ <dd v-if="bridge.deviceInfo.make">{{ bridge.deviceInfo.make }}</dd>
115
+
116
+ <dt v-if="bridge.deviceInfo.model">Model</dt>
117
+ <dd v-if="bridge.deviceInfo.model">{{ bridge.deviceInfo.model }}</dd>
118
+
119
+ <dt v-if="bridge.deviceInfo.serialNumber">Serial Number</dt>
120
+ <dd v-if="bridge.deviceInfo.serialNumber">{{ bridge.deviceInfo.serialNumber }}</dd>
121
+
122
+ <dt v-if="bridge.deviceInfo.firmwareVersion">Firmware Version</dt>
123
+ <dd v-if="bridge.deviceInfo.firmwareVersion">{{ bridge.deviceInfo.firmwareVersion }}</dd>
124
+
125
+ <dt v-if="bridge.deviceInfo.hardwareVersion">Hardware Version</dt>
126
+ <dd v-if="bridge.deviceInfo.hardwareVersion">{{ bridge.deviceInfo.hardwareVersion }}</dd>
127
+ </dl>
128
+ </div>
129
+
130
+ <div v-if="bridge.networkInfo" class="info-section">
131
+ <h3>Network Information</h3>
132
+ <dl>
133
+ <dt v-if="bridge.networkInfo.localIpAddress">Local IP</dt>
134
+ <dd v-if="bridge.networkInfo.localIpAddress">{{ bridge.networkInfo.localIpAddress }}</dd>
135
+
136
+ <dt v-if="bridge.networkInfo.publicIpAddress">Public IP</dt>
137
+ <dd v-if="bridge.networkInfo.publicIpAddress">{{ bridge.networkInfo.publicIpAddress }}</dd>
138
+
139
+ <dt v-if="bridge.networkInfo.macAddress">MAC Address</dt>
140
+ <dd v-if="bridge.networkInfo.macAddress">{{ bridge.networkInfo.macAddress }}</dd>
141
+
142
+ <dt v-if="bridge.networkInfo.gateway">Gateway</dt>
143
+ <dd v-if="bridge.networkInfo.gateway">{{ bridge.networkInfo.gateway }}</dd>
144
+
145
+ <dt v-if="bridge.networkInfo.subnetMask">Subnet Mask</dt>
146
+ <dd v-if="bridge.networkInfo.subnetMask">{{ bridge.networkInfo.subnetMask }}</dd>
147
+ </dl>
148
+ </div>
149
+
150
+ <div v-if="bridge.devicePosition" class="info-section">
151
+ <h3>Location</h3>
152
+ <dl>
153
+ <dt v-if="bridge.devicePosition.latitude !== undefined">Latitude</dt>
154
+ <dd v-if="bridge.devicePosition.latitude !== undefined">{{ bridge.devicePosition.latitude }}</dd>
155
+
156
+ <dt v-if="bridge.devicePosition.longitude !== undefined">Longitude</dt>
157
+ <dd v-if="bridge.devicePosition.longitude !== undefined">{{ bridge.devicePosition.longitude }}</dd>
158
+
159
+ <dt v-if="bridge.devicePosition.floor !== undefined">Floor</dt>
160
+ <dd v-if="bridge.devicePosition.floor !== undefined">{{ bridge.devicePosition.floor }}</dd>
161
+ </dl>
162
+ </div>
163
+
164
+ <div class="info-section">
165
+ <h3>Timestamps</h3>
166
+ <dl>
167
+ <dt v-if="bridge.createdAt">Created</dt>
168
+ <dd v-if="bridge.createdAt">{{ new Date(bridge.createdAt).toLocaleString() }}</dd>
169
+
170
+ <dt v-if="bridge.updatedAt">Updated</dt>
171
+ <dd v-if="bridge.updatedAt">{{ new Date(bridge.updatedAt).toLocaleString() }}</dd>
172
+ </dl>
173
+ </div>
174
+ </div>
175
+ </div>
176
+ </template>
177
+
178
+ <style scoped>
179
+ .bridge-detail {
180
+ max-width: 800px;
181
+ margin: 0 auto;
182
+ }
183
+
184
+ .back-button {
185
+ margin-bottom: 20px;
186
+ background: #666;
187
+ }
188
+
189
+ .back-button:hover {
190
+ background: #555;
191
+ }
192
+
193
+ .bridge-header {
194
+ display: flex;
195
+ justify-content: space-between;
196
+ align-items: center;
197
+ margin-bottom: 30px;
198
+ }
199
+
200
+ .bridge-header h2 {
201
+ margin: 0;
202
+ }
203
+
204
+ .status-badge {
205
+ padding: 6px 12px;
206
+ border-radius: 4px;
207
+ font-size: 0.85rem;
208
+ font-weight: 600;
209
+ text-transform: uppercase;
210
+ }
211
+
212
+ .status-online {
213
+ background: #d4edda;
214
+ color: #155724;
215
+ }
216
+
217
+ .status-offline {
218
+ background: #f8d7da;
219
+ color: #721c24;
220
+ }
221
+
222
+ .status-error {
223
+ background: #fff3cd;
224
+ color: #856404;
225
+ }
226
+
227
+ .status-unknown {
228
+ background: #e2e3e5;
229
+ color: #383d41;
230
+ }
231
+
232
+ .info-section {
233
+ background: #f9f9f9;
234
+ border-radius: 8px;
235
+ padding: 20px;
236
+ margin-bottom: 20px;
237
+ }
238
+
239
+ .info-section h3 {
240
+ margin-top: 0;
241
+ margin-bottom: 15px;
242
+ font-size: 1.1rem;
243
+ color: #333;
244
+ border-bottom: 1px solid #ddd;
245
+ padding-bottom: 10px;
246
+ }
247
+
248
+ dl {
249
+ display: grid;
250
+ grid-template-columns: 150px 1fr;
251
+ gap: 10px;
252
+ margin: 0;
253
+ }
254
+
255
+ dt {
256
+ font-weight: 600;
257
+ color: #555;
258
+ }
259
+
260
+ dd {
261
+ margin: 0;
262
+ color: #333;
263
+ word-break: break-all;
264
+ }
265
+
266
+ .error {
267
+ text-align: center;
268
+ padding: 40px;
269
+ }
270
+
271
+ .error h2 {
272
+ color: #e74c3c;
273
+ margin-bottom: 10px;
274
+ }
275
+
276
+ .error p {
277
+ margin-bottom: 20px;
278
+ }
279
+ </style>