een-api-toolkit 0.3.38 → 0.3.43
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/.claude/agents/een-auth-agent.md +28 -0
- package/.claude/agents/een-events-agent.md +10 -2
- package/.claude/agents/een-setup-agent.md +32 -0
- package/CHANGELOG.md +9 -75
- package/docs/AI-CONTEXT.md +1 -1
- package/docs/ai-reference/AI-AUTH.md +42 -1
- package/docs/ai-reference/AI-DEVICES.md +1 -1
- package/docs/ai-reference/AI-EVENTS.md +2 -2
- package/docs/ai-reference/AI-MEDIA.md +1 -1
- package/docs/ai-reference/AI-SETUP.md +1 -1
- package/docs/ai-reference/AI-USERS.md +1 -1
- package/examples/vue-alerts-metrics/src/App.vue +7 -1
- package/examples/vue-bridges/e2e/auth.spec.ts +97 -0
- package/examples/vue-bridges/src/App.vue +7 -1
- package/examples/vue-cameras/src/App.vue +7 -1
- package/examples/vue-event-subscriptions/src/App.vue +7 -1
- package/examples/vue-event-subscriptions/src/views/LiveEvents.vue +1 -1
- package/examples/vue-events/src/App.vue +7 -1
- package/examples/vue-layouts/src/App.vue +7 -1
- package/examples/vue-users/package-lock.json +2 -2
- package/examples/vue-users/package.json +1 -1
- package/examples/vue-users/src/App.vue +7 -1
- package/package.json +1 -1
|
@@ -122,6 +122,32 @@ authStore.isAuthenticated // Computed: true if valid token exists
|
|
|
122
122
|
authStore.isExpired // Computed: true if token expired
|
|
123
123
|
```
|
|
124
124
|
|
|
125
|
+
### authStore.initialize() - Session Restoration (CRITICAL)
|
|
126
|
+
**Must be called in App.vue onMounted to restore sessions from storage.**
|
|
127
|
+
|
|
128
|
+
Without this call, users must re-login after every page refresh, even with localStorage strategy:
|
|
129
|
+
|
|
130
|
+
```vue
|
|
131
|
+
<script setup lang="ts">
|
|
132
|
+
import { onMounted, computed } from 'vue'
|
|
133
|
+
import { useAuthStore } from 'een-api-toolkit'
|
|
134
|
+
|
|
135
|
+
const authStore = useAuthStore()
|
|
136
|
+
const isAuthenticated = computed(() => authStore.isAuthenticated)
|
|
137
|
+
|
|
138
|
+
// CRITICAL: Restore session from storage on app mount
|
|
139
|
+
onMounted(() => {
|
|
140
|
+
authStore.initialize()
|
|
141
|
+
})
|
|
142
|
+
</script>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
What `initialize()` does:
|
|
146
|
+
1. Loads token, expiration, session ID, base URL from storage
|
|
147
|
+
2. If valid token exists: Sets up auto-refresh timer
|
|
148
|
+
3. If expired token: Clears auth state (user must re-login)
|
|
149
|
+
4. If no token: No action (user must login)
|
|
150
|
+
|
|
125
151
|
## Auth Guard Pattern
|
|
126
152
|
|
|
127
153
|
**CRITICAL**: The OAuth callback check MUST come BEFORE the auth check in the global guard.
|
|
@@ -297,3 +323,5 @@ async function clearAuthState(page: Page): Promise<void> {
|
|
|
297
323
|
| invalid_grant | Code expired or reused | Restart OAuth flow |
|
|
298
324
|
| invalid_state | State mismatch | Clear storage, restart flow |
|
|
299
325
|
| REFRESH_FAILED | Refresh token invalid | Redirect to login |
|
|
326
|
+
| Session lost on refresh | Missing initialize() call | Add `authStore.initialize()` in App.vue onMounted |
|
|
327
|
+
| Must login after refresh | Missing initialize() call | Add `authStore.initialize()` in App.vue onMounted |
|
|
@@ -269,12 +269,20 @@ async function fetchNotifications() {
|
|
|
269
269
|
|
|
270
270
|
## SSE (Server-Sent Events) for Real-Time Updates
|
|
271
271
|
|
|
272
|
+
### SSE Subscription Behavior
|
|
273
|
+
|
|
274
|
+
**Important: TTL is read-only and server-determined**
|
|
275
|
+
- SSE subscriptions have a **15-minute TTL** (900 seconds) set by the server
|
|
276
|
+
- The `timeToLiveSeconds` value **cannot be customized** when creating a subscription
|
|
277
|
+
- The `subscriptionConfig` (including `lifeCycle` and `timeToLiveSeconds`) is returned in the API response but is not a configurable input
|
|
278
|
+
- SSE URLs are **single-use**: once disconnected, you must create a new subscription
|
|
279
|
+
|
|
272
280
|
### SSE Lifecycle
|
|
273
281
|
|
|
274
|
-
1. **Create Subscription** - Get a subscription with SSE URL
|
|
282
|
+
1. **Create Subscription** - Get a subscription with SSE URL (server sets 15-min TTL)
|
|
275
283
|
2. **Connect to Stream** - Open EventSource connection
|
|
276
284
|
3. **Handle Events** - Process events as they arrive
|
|
277
|
-
4. **Cleanup** - Delete subscription when done
|
|
285
|
+
4. **Cleanup** - Delete subscription when done (or it auto-expires after 15 min of inactivity)
|
|
278
286
|
|
|
279
287
|
### createEventSubscription()
|
|
280
288
|
```typescript
|
|
@@ -74,6 +74,35 @@ app.use(router)
|
|
|
74
74
|
app.mount('#app')
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
+
### App.vue Session Restoration (CRITICAL)
|
|
78
|
+
**Users will need to re-login after every page refresh unless you call `authStore.initialize()` in App.vue.**
|
|
79
|
+
|
|
80
|
+
```vue
|
|
81
|
+
<script setup lang="ts">
|
|
82
|
+
import { onMounted, computed } from 'vue'
|
|
83
|
+
import { useAuthStore } from 'een-api-toolkit'
|
|
84
|
+
|
|
85
|
+
const authStore = useAuthStore()
|
|
86
|
+
const isAuthenticated = computed(() => authStore.isAuthenticated)
|
|
87
|
+
|
|
88
|
+
// CRITICAL: Initialize auth store from storage on app mount
|
|
89
|
+
// This restores the session if a valid token exists in localStorage/sessionStorage
|
|
90
|
+
onMounted(() => {
|
|
91
|
+
authStore.initialize()
|
|
92
|
+
})
|
|
93
|
+
</script>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Without `initialize()`:
|
|
97
|
+
- Token is saved to localStorage after login ✓
|
|
98
|
+
- On page refresh, Pinia store starts with empty state
|
|
99
|
+
- `isAuthenticated` returns false → user must login again
|
|
100
|
+
|
|
101
|
+
With `initialize()`:
|
|
102
|
+
- Token is loaded from localStorage on app mount
|
|
103
|
+
- `isAuthenticated` returns true → session is restored
|
|
104
|
+
- User can continue without re-logging in
|
|
105
|
+
|
|
77
106
|
### vite.config.ts for EEN OAuth
|
|
78
107
|
```typescript
|
|
79
108
|
import { defineConfig } from 'vite'
|
|
@@ -115,6 +144,7 @@ export default router
|
|
|
115
144
|
- Pinia must be installed before initEenToolkit()
|
|
116
145
|
- Never add trailing slashes to redirect URIs
|
|
117
146
|
- Ensure VITE_PROXY_URL is set in .env file
|
|
147
|
+
- **Always call `authStore.initialize()` in App.vue onMounted** for session persistence
|
|
118
148
|
|
|
119
149
|
## Common Errors and Solutions
|
|
120
150
|
|
|
@@ -124,3 +154,5 @@ export default router
|
|
|
124
154
|
| "redirect_uri mismatch" | Wrong host/port | Use 127.0.0.1:3333 in vite.config.ts |
|
|
125
155
|
| "Invalid redirect_uri" | Trailing slash | Remove trailing slash from redirect URI |
|
|
126
156
|
| "CORS error" | Proxy not running | Start the OAuth proxy server |
|
|
157
|
+
| Session lost on refresh | Missing initialize() call | Add `authStore.initialize()` in App.vue onMounted |
|
|
158
|
+
| Must login after refresh | Missing initialize() call | Add `authStore.initialize()` in App.vue onMounted |
|
package/CHANGELOG.md
CHANGED
|
@@ -2,92 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
-
## [0.3.
|
|
5
|
+
## [0.3.43] - 2026-01-25
|
|
6
6
|
|
|
7
7
|
### Release Summary
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
## Summary
|
|
11
|
-
- Add missing `een-grouping-agent` to the setup-agents.ts script
|
|
12
|
-
- Fix timezone issue in datetime persistence E2E test (vue-media)
|
|
13
|
-
- Increase AUTH_COMPLETE timeout for better CI stability
|
|
14
|
-
|
|
15
|
-
## Changes
|
|
16
|
-
- `3f04224` fix: Fix timezone issue in datetime persistence E2E test
|
|
17
|
-
- `fbf300c` fix: Add missing een-grouping-agent to setup script
|
|
18
|
-
- `ea6ba9a` fix: Increase AUTH_COMPLETE timeout for CI stability
|
|
19
|
-
|
|
20
|
-
## Test Results
|
|
21
|
-
- ✅ Lint: passed
|
|
22
|
-
- ✅ Unit tests: 378 passed
|
|
23
|
-
- ✅ Build: successful
|
|
24
|
-
- ✅ E2E tests: 137 passed across 9 example apps
|
|
25
|
-
|
|
26
|
-
## Version
|
|
27
|
-
`0.3.35`
|
|
28
|
-
|
|
29
|
-
🤖 Generated with [Claude Code](https://claude.ai/code)
|
|
30
|
-
|
|
31
|
-
#### PR #75: fix: Skip datetime persistence test in CI environments
|
|
32
|
-
## Summary
|
|
33
|
-
Skip the datetime persistence E2E test in CI environments to avoid timezone-related flaky failures.
|
|
34
|
-
|
|
35
|
-
## Changes
|
|
36
|
-
- `fa1d667` fix: Skip datetime persistence test in CI environments
|
|
37
|
-
|
|
38
|
-
## Details
|
|
39
|
-
The datetime persistence test relies on local timezone calculations that produce inconsistent results between CI (UTC) and local development environments. The test passes locally but fails in CI due to 1-hour timezone offsets.
|
|
40
|
-
|
|
41
|
-
## Version
|
|
42
|
-
`0.3.35`
|
|
43
|
-
|
|
44
|
-
🤖 Generated with [Claude Code](https://claude.ai/code)
|
|
45
|
-
|
|
46
|
-
#### PR #77: fix: UTC timezone for datetime test and include examples in version bump
|
|
47
|
-
## Summary
|
|
48
|
-
- Force UTC timezone in datetime persistence test for consistent CI/local behavior
|
|
49
|
-
- Include examples folder in Husky version bump trigger
|
|
50
|
-
|
|
51
|
-
## Changes
|
|
52
|
-
- `3641923` fix: Force UTC timezone in datetime persistence test for CI consistency
|
|
53
|
-
- `3a40255` chore: Include examples folder in version bump trigger
|
|
54
|
-
- `0a99fb8` chore: Bump version to 0.3.37
|
|
55
|
-
|
|
56
|
-
## Details
|
|
57
|
-
|
|
58
|
-
### UTC Timezone Fix
|
|
59
|
-
Instead of skipping the datetime persistence test in CI, the browser is now forced to use UTC timezone by overriding `Date.getTimezoneOffset()` via Playwright's `addInitScript`. This ensures consistent behavior across CI (UTC) and local development environments.
|
|
60
|
-
|
|
61
|
-
Addresses issue #76.
|
|
62
|
-
|
|
63
|
-
### Husky Update
|
|
64
|
-
Changes to `examples/**/*` now trigger a package version increment, ensuring example app updates are properly versioned.
|
|
65
|
-
|
|
66
|
-
## Version
|
|
67
|
-
`0.3.37`
|
|
68
|
-
|
|
69
|
-
🤖 Generated with [Claude Code](https://claude.ai/code)
|
|
70
|
-
|
|
9
|
+
No PR descriptions available for this release.
|
|
71
10
|
|
|
72
11
|
### Detailed Changes
|
|
73
12
|
|
|
74
13
|
#### Bug Fixes
|
|
75
|
-
- fix:
|
|
76
|
-
- fix: Force UTC timezone in datetime persistence test for CI consistency
|
|
77
|
-
- fix: Skip datetime persistence test in CI environments
|
|
78
|
-
- fix: Fix timezone issue in datetime persistence E2E test
|
|
79
|
-
- fix: Add missing een-grouping-agent to setup script
|
|
14
|
+
- fix: Add authStore.initialize() to all example apps for session persistence
|
|
80
15
|
|
|
81
16
|
#### Other Changes
|
|
82
|
-
-
|
|
83
|
-
-
|
|
84
|
-
-
|
|
85
|
-
-
|
|
86
|
-
- docs: Add een-grouping-agent to CLAUDE.md and improve E2E test
|
|
17
|
+
- test: Add session persistence tests for login/logout and multi-tab scenarios
|
|
18
|
+
- test: Add auth store session persistence tests for all storage strategies
|
|
19
|
+
- chore: Add Session Persistence section to AI doc generation
|
|
20
|
+
- docs: Clarify SSE subscription TTL is read-only and server-determined
|
|
87
21
|
|
|
88
22
|
### Links
|
|
89
23
|
- [npm package](https://www.npmjs.com/package/een-api-toolkit)
|
|
90
|
-
- [Full Changelog](https://github.com/klaushofrichter/een-api-toolkit/compare/v0.3.
|
|
24
|
+
- [Full Changelog](https://github.com/klaushofrichter/een-api-toolkit/compare/v0.3.38...v0.3.43)
|
|
91
25
|
|
|
92
26
|
---
|
|
93
|
-
*Released: 2026-01-
|
|
27
|
+
*Released: 2026-01-25 12:06:28 CST*
|
package/docs/AI-CONTEXT.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Authentication - EEN API Toolkit
|
|
2
2
|
|
|
3
|
-
> **Version:** 0.3.
|
|
3
|
+
> **Version:** 0.3.43
|
|
4
4
|
>
|
|
5
5
|
> OAuth flow implementation, token management, and session handling.
|
|
6
6
|
> Load this document when implementing login, logout, or auth guards.
|
|
@@ -98,6 +98,47 @@ authStore.sessionId // Session identifier
|
|
|
98
98
|
|
|
99
99
|
---
|
|
100
100
|
|
|
101
|
+
## Session Persistence (IMPORTANT)
|
|
102
|
+
|
|
103
|
+
**To restore sessions from storage on page load, you must call `authStore.initialize()` in your App.vue.**
|
|
104
|
+
|
|
105
|
+
Without this call, users will need to log in again after every page refresh, even with `localStorage` strategy.
|
|
106
|
+
|
|
107
|
+
### App.vue Setup
|
|
108
|
+
|
|
109
|
+
```vue
|
|
110
|
+
<script setup lang="ts">
|
|
111
|
+
import { onMounted, computed } from 'vue'
|
|
112
|
+
import { useAuthStore } from 'een-api-toolkit'
|
|
113
|
+
|
|
114
|
+
const authStore = useAuthStore()
|
|
115
|
+
const isAuthenticated = computed(() => authStore.isAuthenticated)
|
|
116
|
+
|
|
117
|
+
// CRITICAL: Initialize auth store from storage on app mount
|
|
118
|
+
// This restores the session if a valid token exists in localStorage/sessionStorage
|
|
119
|
+
onMounted(() => {
|
|
120
|
+
authStore.initialize()
|
|
121
|
+
})
|
|
122
|
+
</script>
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### What initialize() Does
|
|
126
|
+
|
|
127
|
+
1. Loads token, expiration, session ID, and base URL from configured storage
|
|
128
|
+
2. If token exists and is **not expired**: Sets up auto-refresh timer
|
|
129
|
+
3. If token exists but **is expired**: Clears auth state (user must re-login)
|
|
130
|
+
4. If no token: No action (user must login)
|
|
131
|
+
|
|
132
|
+
### Storage Strategy Behavior
|
|
133
|
+
|
|
134
|
+
| Strategy | Persists Across Refresh? | Requires initialize()? |
|
|
135
|
+
|----------|-------------------------|------------------------|
|
|
136
|
+
| `localStorage` | Yes | Yes |
|
|
137
|
+
| `sessionStorage` | Yes (within tab) | Yes |
|
|
138
|
+
| `memory` | No | No (always requires re-login) |
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
101
142
|
## Vue Router Auth Guard
|
|
102
143
|
|
|
103
144
|
Protect routes that require authentication:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Events, Alerts & Real-Time Streaming - EEN API Toolkit
|
|
2
2
|
|
|
3
|
-
> **Version:** 0.3.
|
|
3
|
+
> **Version:** 0.3.43
|
|
4
4
|
>
|
|
5
5
|
> Complete reference for events, alerts, metrics, and SSE subscriptions.
|
|
6
6
|
> Load this document when implementing event-driven features.
|
|
@@ -1632,7 +1632,7 @@ watch(selectedSubscriptionId, (newId) => {
|
|
|
1632
1632
|
<ul class="warning-list">
|
|
1633
1633
|
<li>SSE URLs are single-use. Once disconnected, the subscription cannot be reconnected.</li>
|
|
1634
1634
|
<li>To receive events again after disconnecting, create a new subscription.</li>
|
|
1635
|
-
<li>
|
|
1635
|
+
<li>SSE subscriptions have a server-determined 15-minute TTL (not configurable) and expire if not connected.</li>
|
|
1636
1636
|
</ul>
|
|
1637
1637
|
</div>
|
|
1638
1638
|
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { onMounted, computed } from 'vue'
|
|
2
3
|
import { useAuthStore } from 'een-api-toolkit'
|
|
3
|
-
import { computed } from 'vue'
|
|
4
4
|
|
|
5
5
|
const authStore = useAuthStore()
|
|
6
6
|
const isAuthenticated = computed(() => authStore.isAuthenticated)
|
|
7
|
+
|
|
8
|
+
// Initialize auth store from storage on app mount
|
|
9
|
+
// This restores the session if a valid token exists in localStorage/sessionStorage
|
|
10
|
+
onMounted(() => {
|
|
11
|
+
authStore.initialize()
|
|
12
|
+
})
|
|
7
13
|
</script>
|
|
8
14
|
|
|
9
15
|
<template>
|
|
@@ -203,4 +203,101 @@ test.describe('Vue Bridges Example - Auth', () => {
|
|
|
203
203
|
await expect(page.locator('[data-testid="not-authenticated"]')).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
|
|
204
204
|
await expect(page.locator('[data-testid="nav-login"]')).toBeVisible()
|
|
205
205
|
})
|
|
206
|
+
|
|
207
|
+
test('after logout, localStorage is cleared and new login flow is required', async ({ page }) => {
|
|
208
|
+
skipIfNoProxy()
|
|
209
|
+
skipIfNoCredentials()
|
|
210
|
+
|
|
211
|
+
// First login
|
|
212
|
+
await performLogin(page, TEST_USER!, TEST_PASSWORD!)
|
|
213
|
+
await expect(page.locator('[data-testid="nav-logout"]')).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
|
|
214
|
+
|
|
215
|
+
// Verify we're authenticated
|
|
216
|
+
await expect(page.locator('[data-testid="nav-bridges"]')).toBeVisible()
|
|
217
|
+
|
|
218
|
+
// Logout
|
|
219
|
+
await page.click('[data-testid="nav-logout"]')
|
|
220
|
+
await page.waitForURL('**/')
|
|
221
|
+
await expect(page.locator('[data-testid="not-authenticated"]')).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
|
|
222
|
+
|
|
223
|
+
// Verify localStorage is cleared (session data removed)
|
|
224
|
+
const tokenAfterLogout = await page.evaluate(() => localStorage.getItem('een_token'))
|
|
225
|
+
expect(tokenAfterLogout).toBeNull()
|
|
226
|
+
|
|
227
|
+
const sessionIdAfterLogout = await page.evaluate(() => localStorage.getItem('een_sessionId'))
|
|
228
|
+
expect(sessionIdAfterLogout).toBeNull()
|
|
229
|
+
|
|
230
|
+
const hostnameAfterLogout = await page.evaluate(() => localStorage.getItem('een_hostname'))
|
|
231
|
+
expect(hostnameAfterLogout).toBeNull()
|
|
232
|
+
|
|
233
|
+
// Verify the app shows not-authenticated state (proves our session is cleared)
|
|
234
|
+
await expect(page.locator('[data-testid="not-authenticated"]')).toBeVisible()
|
|
235
|
+
await expect(page.locator('[data-testid="nav-login"]')).toBeVisible()
|
|
236
|
+
await expect(page.locator('[data-testid="nav-logout"]')).not.toBeVisible()
|
|
237
|
+
|
|
238
|
+
// Click login to start new OAuth flow
|
|
239
|
+
await page.click('[data-testid="login-button"]')
|
|
240
|
+
await page.waitForURL('/login')
|
|
241
|
+
|
|
242
|
+
// Click OAuth login button - this initiates the OAuth redirect
|
|
243
|
+
await page.click('button:has-text("Login with Eagle Eye Networks")')
|
|
244
|
+
|
|
245
|
+
// Wait for either:
|
|
246
|
+
// 1. OAuth page (if IDP session expired) - user needs to enter credentials
|
|
247
|
+
// 2. Callback/bridges page (if IDP remembers user via cookies) - auto-login
|
|
248
|
+
// Either way, our localStorage was cleared and user had to initiate a new login
|
|
249
|
+
await page.waitForURL(/eagleeyenetworks\.com|127\.0\.0\.1:3333/, { timeout: TIMEOUTS.AUTH_COMPLETE })
|
|
250
|
+
|
|
251
|
+
// If we ended up back at our app (auto-login via IDP cookies), verify we're authenticated again
|
|
252
|
+
const currentUrl = page.url()
|
|
253
|
+
if (currentUrl.includes('127.0.0.1:3333')) {
|
|
254
|
+
// Auto-logged in - wait for the full flow to complete
|
|
255
|
+
await page.waitForURL('**/bridges', { timeout: TIMEOUTS.AUTH_COMPLETE })
|
|
256
|
+
await expect(page.locator('[data-testid="nav-bridges"]')).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
|
|
257
|
+
await expect(page.locator('[data-testid="nav-logout"]')).toBeVisible()
|
|
258
|
+
|
|
259
|
+
// Verify localStorage has new token (proves it was cleared and refilled)
|
|
260
|
+
const newToken = await page.evaluate(() => localStorage.getItem('een_token'))
|
|
261
|
+
expect(newToken).not.toBeNull()
|
|
262
|
+
} else {
|
|
263
|
+
// At OAuth page - user needs to enter credentials
|
|
264
|
+
const emailInput = page.locator('#authentication--input__email')
|
|
265
|
+
await expect(emailInput).toBeVisible({ timeout: TIMEOUTS.ELEMENT_VISIBLE })
|
|
266
|
+
}
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
test('localStorage: second tab shares session without re-login', async ({ page, context }) => {
|
|
270
|
+
skipIfNoProxy()
|
|
271
|
+
skipIfNoCredentials()
|
|
272
|
+
|
|
273
|
+
// First tab: login
|
|
274
|
+
await performLogin(page, TEST_USER!, TEST_PASSWORD!)
|
|
275
|
+
await expect(page.locator('[data-testid="nav-logout"]')).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
|
|
276
|
+
|
|
277
|
+
// Verify localStorage has token
|
|
278
|
+
const tokenInFirstTab = await page.evaluate(() => localStorage.getItem('een_token'))
|
|
279
|
+
expect(tokenInFirstTab).not.toBeNull()
|
|
280
|
+
|
|
281
|
+
// Open second tab in same browser context (shares localStorage)
|
|
282
|
+
const secondTab = await context.newPage()
|
|
283
|
+
await secondTab.goto('/')
|
|
284
|
+
|
|
285
|
+
// Second tab should be authenticated immediately (no login needed)
|
|
286
|
+
// because localStorage is shared and App.vue calls initialize()
|
|
287
|
+
await expect(secondTab.locator('[data-testid="nav-logout"]')).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
|
|
288
|
+
await expect(secondTab.locator('[data-testid="nav-bridges"]')).toBeVisible()
|
|
289
|
+
await expect(secondTab.locator('[data-testid="nav-login"]')).not.toBeVisible()
|
|
290
|
+
|
|
291
|
+
// Verify second tab has the same token from localStorage
|
|
292
|
+
const tokenInSecondTab = await secondTab.evaluate(() => localStorage.getItem('een_token'))
|
|
293
|
+
expect(tokenInSecondTab).toBe(tokenInFirstTab)
|
|
294
|
+
|
|
295
|
+
// Navigate to bridges in second tab - should work without login
|
|
296
|
+
await secondTab.click('[data-testid="nav-bridges"]')
|
|
297
|
+
await secondTab.waitForURL('**/bridges')
|
|
298
|
+
await expect(secondTab.locator('.bridge-grid, .no-bridges')).toBeVisible({ timeout: TIMEOUTS.ELEMENT_VISIBLE })
|
|
299
|
+
|
|
300
|
+
// Clean up second tab
|
|
301
|
+
await secondTab.close()
|
|
302
|
+
})
|
|
206
303
|
})
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { onMounted, computed } from 'vue'
|
|
2
3
|
import { useAuthStore } from 'een-api-toolkit'
|
|
3
|
-
import { computed } from 'vue'
|
|
4
4
|
|
|
5
5
|
const authStore = useAuthStore()
|
|
6
6
|
const isAuthenticated = computed(() => authStore.isAuthenticated)
|
|
7
|
+
|
|
8
|
+
// Initialize auth store from storage on app mount
|
|
9
|
+
// This restores the session if a valid token exists in localStorage/sessionStorage
|
|
10
|
+
onMounted(() => {
|
|
11
|
+
authStore.initialize()
|
|
12
|
+
})
|
|
7
13
|
</script>
|
|
8
14
|
|
|
9
15
|
<template>
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { onMounted, computed } from 'vue'
|
|
2
3
|
import { useAuthStore } from 'een-api-toolkit'
|
|
3
|
-
import { computed } from 'vue'
|
|
4
4
|
|
|
5
5
|
const authStore = useAuthStore()
|
|
6
6
|
const isAuthenticated = computed(() => authStore.isAuthenticated)
|
|
7
|
+
|
|
8
|
+
// Initialize auth store from storage on app mount
|
|
9
|
+
// This restores the session if a valid token exists in localStorage/sessionStorage
|
|
10
|
+
onMounted(() => {
|
|
11
|
+
authStore.initialize()
|
|
12
|
+
})
|
|
7
13
|
</script>
|
|
8
14
|
|
|
9
15
|
<template>
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { onMounted, computed } from 'vue'
|
|
2
3
|
import { useAuthStore } from 'een-api-toolkit'
|
|
3
|
-
import { computed } from 'vue'
|
|
4
4
|
import { useRouter } from 'vue-router'
|
|
5
5
|
|
|
6
6
|
const authStore = useAuthStore()
|
|
7
7
|
const router = useRouter()
|
|
8
8
|
|
|
9
9
|
const isAuthenticated = computed(() => authStore.isAuthenticated)
|
|
10
|
+
|
|
11
|
+
// Initialize auth store from storage on app mount
|
|
12
|
+
// This restores the session if a valid token exists in localStorage/sessionStorage
|
|
13
|
+
onMounted(() => {
|
|
14
|
+
authStore.initialize()
|
|
15
|
+
})
|
|
10
16
|
const refreshFailed = computed(() => authStore.refreshFailed)
|
|
11
17
|
const refreshFailedMessage = computed(() => authStore.refreshFailedMessage)
|
|
12
18
|
const isRefreshing = computed(() => authStore.isRefreshing)
|
|
@@ -380,7 +380,7 @@ watch(selectedSubscriptionId, (newId) => {
|
|
|
380
380
|
<ul class="warning-list">
|
|
381
381
|
<li>SSE URLs are single-use. Once disconnected, the subscription cannot be reconnected.</li>
|
|
382
382
|
<li>To receive events again after disconnecting, create a new subscription.</li>
|
|
383
|
-
<li>
|
|
383
|
+
<li>SSE subscriptions have a server-determined 15-minute TTL (not configurable) and expire if not connected.</li>
|
|
384
384
|
</ul>
|
|
385
385
|
</div>
|
|
386
386
|
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { onMounted, computed } from 'vue'
|
|
2
3
|
import { useAuthStore } from 'een-api-toolkit'
|
|
3
|
-
import { computed } from 'vue'
|
|
4
4
|
|
|
5
5
|
const authStore = useAuthStore()
|
|
6
6
|
const isAuthenticated = computed(() => authStore.isAuthenticated)
|
|
7
|
+
|
|
8
|
+
// Initialize auth store from storage on app mount
|
|
9
|
+
// This restores the session if a valid token exists in localStorage/sessionStorage
|
|
10
|
+
onMounted(() => {
|
|
11
|
+
authStore.initialize()
|
|
12
|
+
})
|
|
7
13
|
</script>
|
|
8
14
|
|
|
9
15
|
<template>
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { onMounted, computed } from 'vue'
|
|
2
3
|
import { useAuthStore } from 'een-api-toolkit'
|
|
3
|
-
import { computed } from 'vue'
|
|
4
4
|
|
|
5
5
|
const authStore = useAuthStore()
|
|
6
6
|
const isAuthenticated = computed(() => authStore.isAuthenticated)
|
|
7
|
+
|
|
8
|
+
// Initialize auth store from storage on app mount
|
|
9
|
+
// This restores the session if a valid token exists in localStorage/sessionStorage
|
|
10
|
+
onMounted(() => {
|
|
11
|
+
authStore.initialize()
|
|
12
|
+
})
|
|
7
13
|
</script>
|
|
8
14
|
|
|
9
15
|
<template>
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "een-api-toolkit-example",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.26",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "een-api-toolkit-example",
|
|
9
|
-
"version": "0.0.
|
|
9
|
+
"version": "0.0.26",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"een-api-toolkit": "file:../..",
|
|
12
12
|
"pinia": "^3.0.4",
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { onMounted, computed } from 'vue'
|
|
2
3
|
import { useAuthStore } from 'een-api-toolkit'
|
|
3
|
-
import { computed } from 'vue'
|
|
4
4
|
|
|
5
5
|
const authStore = useAuthStore()
|
|
6
6
|
const isAuthenticated = computed(() => authStore.isAuthenticated)
|
|
7
|
+
|
|
8
|
+
// Initialize auth store from storage on app mount
|
|
9
|
+
// This restores the session if a valid token exists in localStorage/sessionStorage
|
|
10
|
+
onMounted(() => {
|
|
11
|
+
authStore.initialize()
|
|
12
|
+
})
|
|
7
13
|
</script>
|
|
8
14
|
|
|
9
15
|
<template>
|