een-api-toolkit 0.3.15 → 0.3.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +45 -6
- package/README.md +1 -0
- package/dist/index.cjs +3 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +585 -0
- package/dist/index.js +485 -261
- package/dist/index.js.map +1 -1
- package/docs/AI-CONTEXT.md +144 -1
- package/examples/vue-alerts-metrics/e2e/auth.spec.ts +8 -1
- package/examples/vue-alerts-metrics/package-lock.json +8 -1
- package/examples/vue-alerts-metrics/package.json +4 -3
- package/examples/vue-alerts-metrics/src/components/AlertsList.vue +567 -16
- package/examples/vue-alerts-metrics/src/components/CameraSelector.vue +16 -6
- package/examples/vue-alerts-metrics/src/components/MetricsChart.vue +23 -9
- package/examples/vue-alerts-metrics/src/components/NotificationsList.vue +579 -17
- package/examples/vue-alerts-metrics/src/components/TimeRangeSelector.vue +197 -12
- package/examples/vue-alerts-metrics/src/composables/useHlsPlayer.ts +285 -0
- package/examples/vue-alerts-metrics/src/views/Dashboard.vue +31 -9
- package/examples/vue-alerts-metrics/src/views/Home.vue +56 -7
- package/examples/vue-event-subscriptions/.env.example +15 -0
- package/examples/vue-event-subscriptions/README.md +103 -0
- package/examples/vue-event-subscriptions/e2e/app.spec.ts +71 -0
- package/examples/vue-event-subscriptions/e2e/auth.spec.ts +290 -0
- package/examples/vue-event-subscriptions/index.html +13 -0
- package/examples/vue-event-subscriptions/package-lock.json +1719 -0
- package/examples/vue-event-subscriptions/package.json +28 -0
- package/examples/vue-event-subscriptions/playwright.config.ts +47 -0
- package/examples/vue-event-subscriptions/src/App.vue +233 -0
- package/examples/vue-event-subscriptions/src/main.ts +25 -0
- package/examples/vue-event-subscriptions/src/router/index.ts +68 -0
- package/examples/vue-event-subscriptions/src/views/Callback.vue +76 -0
- package/examples/vue-event-subscriptions/src/views/Home.vue +192 -0
- package/examples/vue-event-subscriptions/src/views/LiveEvents.vue +640 -0
- package/examples/vue-event-subscriptions/src/views/Login.vue +33 -0
- package/examples/vue-event-subscriptions/src/views/Logout.vue +59 -0
- package/examples/vue-event-subscriptions/src/views/Subscriptions.vue +402 -0
- package/examples/vue-event-subscriptions/src/vite-env.d.ts +12 -0
- package/examples/vue-event-subscriptions/tsconfig.json +21 -0
- package/examples/vue-event-subscriptions/tsconfig.node.json +10 -0
- package/examples/vue-event-subscriptions/vite.config.ts +12 -0
- package/package.json +1 -1
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { onMounted, ref } from 'vue'
|
|
3
|
+
import { useRouter } from 'vue-router'
|
|
4
|
+
import { revokeToken } from 'een-api-toolkit'
|
|
5
|
+
|
|
6
|
+
const router = useRouter()
|
|
7
|
+
const processing = ref(true)
|
|
8
|
+
const error = ref<string | null>(null)
|
|
9
|
+
|
|
10
|
+
onMounted(async () => {
|
|
11
|
+
const result = await revokeToken()
|
|
12
|
+
|
|
13
|
+
if (result.error) {
|
|
14
|
+
// Even if revoke fails, the local state is cleared
|
|
15
|
+
console.warn('Token revocation failed:', result.error.message)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
processing.value = false
|
|
19
|
+
|
|
20
|
+
// Redirect to home after a short delay
|
|
21
|
+
setTimeout(() => {
|
|
22
|
+
router.push('/')
|
|
23
|
+
}, 2000)
|
|
24
|
+
})
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<template>
|
|
28
|
+
<div class="logout">
|
|
29
|
+
<div v-if="processing">
|
|
30
|
+
<h2>Logging out...</h2>
|
|
31
|
+
<p class="loading">Please wait.</p>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<div v-else>
|
|
35
|
+
<h2>Logged Out</h2>
|
|
36
|
+
<p>You have been successfully logged out.</p>
|
|
37
|
+
<p v-if="error" class="error">Note: {{ error }}</p>
|
|
38
|
+
<p class="redirect">Redirecting to home page...</p>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</template>
|
|
42
|
+
|
|
43
|
+
<style scoped>
|
|
44
|
+
.logout {
|
|
45
|
+
text-align: center;
|
|
46
|
+
max-width: 400px;
|
|
47
|
+
margin: 0 auto;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
h2 {
|
|
51
|
+
margin-bottom: 20px;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.redirect {
|
|
55
|
+
color: #666;
|
|
56
|
+
font-style: italic;
|
|
57
|
+
margin-top: 20px;
|
|
58
|
+
}
|
|
59
|
+
</style>
|
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, computed, onMounted } from 'vue'
|
|
3
|
+
import {
|
|
4
|
+
listEventSubscriptions,
|
|
5
|
+
createEventSubscription,
|
|
6
|
+
deleteEventSubscription,
|
|
7
|
+
getCameras,
|
|
8
|
+
listEventTypes,
|
|
9
|
+
type EventSubscription,
|
|
10
|
+
type Camera,
|
|
11
|
+
type EventType,
|
|
12
|
+
type EenError,
|
|
13
|
+
type ListEventSubscriptionsParams
|
|
14
|
+
} from 'een-api-toolkit'
|
|
15
|
+
|
|
16
|
+
// Subscriptions state
|
|
17
|
+
const subscriptions = ref<EventSubscription[]>([])
|
|
18
|
+
const loading = ref(false)
|
|
19
|
+
const error = ref<EenError | null>(null)
|
|
20
|
+
const nextPageToken = ref<string | undefined>(undefined)
|
|
21
|
+
const hasNextPage = computed(() => !!nextPageToken.value)
|
|
22
|
+
|
|
23
|
+
// Create form state
|
|
24
|
+
const cameras = ref<Camera[]>([])
|
|
25
|
+
const eventTypes = ref<EventType[]>([])
|
|
26
|
+
const selectedCameras = ref<string[]>([])
|
|
27
|
+
const selectedEventTypes = ref<string[]>([])
|
|
28
|
+
const creating = ref(false)
|
|
29
|
+
const createError = ref<EenError | null>(null)
|
|
30
|
+
const createSuccess = ref(false)
|
|
31
|
+
|
|
32
|
+
// Delete state
|
|
33
|
+
const deleting = ref<string | null>(null)
|
|
34
|
+
|
|
35
|
+
const params = ref<ListEventSubscriptionsParams>({ pageSize: 10 })
|
|
36
|
+
|
|
37
|
+
async function fetchSubscriptions(fetchParams?: ListEventSubscriptionsParams, append = false) {
|
|
38
|
+
loading.value = true
|
|
39
|
+
error.value = null
|
|
40
|
+
|
|
41
|
+
const mergedParams = { ...params.value, ...fetchParams }
|
|
42
|
+
const result = await listEventSubscriptions(mergedParams)
|
|
43
|
+
|
|
44
|
+
if (result.error) {
|
|
45
|
+
error.value = result.error
|
|
46
|
+
if (!append) {
|
|
47
|
+
subscriptions.value = []
|
|
48
|
+
}
|
|
49
|
+
nextPageToken.value = undefined
|
|
50
|
+
} else {
|
|
51
|
+
if (append) {
|
|
52
|
+
subscriptions.value = [...subscriptions.value, ...result.data.results]
|
|
53
|
+
} else {
|
|
54
|
+
subscriptions.value = result.data.results
|
|
55
|
+
}
|
|
56
|
+
nextPageToken.value = result.data.nextPageToken
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
loading.value = false
|
|
60
|
+
return result
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function refresh() {
|
|
64
|
+
return fetchSubscriptions()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function fetchNextPage() {
|
|
68
|
+
if (!nextPageToken.value) return
|
|
69
|
+
return fetchSubscriptions({ ...params.value, pageToken: nextPageToken.value }, true)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Validates and formats an actor ID for event subscriptions.
|
|
74
|
+
* Actor IDs must follow the format "type:id" (e.g., "camera:abc123").
|
|
75
|
+
* @param type - The actor type (e.g., "camera", "bridge")
|
|
76
|
+
* @param id - The actor ID (alphanumeric, hyphens, underscores allowed)
|
|
77
|
+
* @returns The formatted actor ID or null if invalid
|
|
78
|
+
*/
|
|
79
|
+
function formatActorId(type: string, id: string): string | null {
|
|
80
|
+
// Validate actor type (only allow known types)
|
|
81
|
+
const validTypes = ['camera', 'bridge', 'account', 'user']
|
|
82
|
+
if (!validTypes.includes(type)) {
|
|
83
|
+
return null
|
|
84
|
+
}
|
|
85
|
+
// Validate ID format (alphanumeric with hyphens and underscores)
|
|
86
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(id)) {
|
|
87
|
+
return null
|
|
88
|
+
}
|
|
89
|
+
return `${type}:${id}`
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function loadFormData() {
|
|
93
|
+
// Load cameras
|
|
94
|
+
const camerasResult = await getCameras({ pageSize: 100 })
|
|
95
|
+
if (!camerasResult.error) {
|
|
96
|
+
cameras.value = camerasResult.data.results
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Load event types
|
|
100
|
+
const eventTypesResult = await listEventTypes({ pageSize: 100 })
|
|
101
|
+
if (!eventTypesResult.error) {
|
|
102
|
+
eventTypes.value = eventTypesResult.data.results
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function handleCreate() {
|
|
107
|
+
if (selectedCameras.value.length === 0 || selectedEventTypes.value.length === 0) {
|
|
108
|
+
createError.value = { code: 'VALIDATION_ERROR', message: 'Please select at least one camera and one event type' }
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
creating.value = true
|
|
113
|
+
createError.value = null
|
|
114
|
+
createSuccess.value = false
|
|
115
|
+
|
|
116
|
+
// Validate and format actor IDs
|
|
117
|
+
const actors: string[] = []
|
|
118
|
+
for (const id of selectedCameras.value) {
|
|
119
|
+
const actorId = formatActorId('camera', id)
|
|
120
|
+
if (!actorId) {
|
|
121
|
+
createError.value = { code: 'VALIDATION_ERROR', message: `Invalid camera ID format: ${id}` }
|
|
122
|
+
creating.value = false
|
|
123
|
+
return
|
|
124
|
+
}
|
|
125
|
+
actors.push(actorId)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const result = await createEventSubscription({
|
|
129
|
+
deliveryConfig: { type: 'serverSentEvents.v1' },
|
|
130
|
+
filters: [{
|
|
131
|
+
actors,
|
|
132
|
+
types: selectedEventTypes.value.map(type => ({ id: type }))
|
|
133
|
+
}]
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
if (result.error) {
|
|
137
|
+
createError.value = result.error
|
|
138
|
+
} else {
|
|
139
|
+
createSuccess.value = true
|
|
140
|
+
selectedCameras.value = []
|
|
141
|
+
selectedEventTypes.value = []
|
|
142
|
+
// Refresh the list
|
|
143
|
+
await fetchSubscriptions()
|
|
144
|
+
// Hide success message after 3 seconds
|
|
145
|
+
setTimeout(() => {
|
|
146
|
+
createSuccess.value = false
|
|
147
|
+
}, 3000)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
creating.value = false
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function handleDelete(subscriptionId: string) {
|
|
154
|
+
if (!confirm('Are you sure you want to delete this subscription?')) {
|
|
155
|
+
return
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
deleting.value = subscriptionId
|
|
159
|
+
const result = await deleteEventSubscription(subscriptionId)
|
|
160
|
+
|
|
161
|
+
if (result.error) {
|
|
162
|
+
error.value = result.error
|
|
163
|
+
} else {
|
|
164
|
+
// Remove from local list
|
|
165
|
+
subscriptions.value = subscriptions.value.filter(s => s.id !== subscriptionId)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
deleting.value = null
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function getDeliveryType(sub: EventSubscription): string {
|
|
172
|
+
return sub.deliveryConfig.type
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function getSseUrl(sub: EventSubscription): string | undefined {
|
|
176
|
+
if (sub.deliveryConfig.type === 'serverSentEvents.v1') {
|
|
177
|
+
return sub.deliveryConfig.sseUrl
|
|
178
|
+
}
|
|
179
|
+
return undefined
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
onMounted(async () => {
|
|
183
|
+
await Promise.all([
|
|
184
|
+
fetchSubscriptions(),
|
|
185
|
+
loadFormData()
|
|
186
|
+
])
|
|
187
|
+
})
|
|
188
|
+
</script>
|
|
189
|
+
|
|
190
|
+
<template>
|
|
191
|
+
<div class="subscriptions">
|
|
192
|
+
<div class="header">
|
|
193
|
+
<h2>Event Subscriptions</h2>
|
|
194
|
+
<button @click="refresh" :disabled="loading">
|
|
195
|
+
{{ loading ? 'Loading...' : 'Refresh' }}
|
|
196
|
+
</button>
|
|
197
|
+
</div>
|
|
198
|
+
|
|
199
|
+
<!-- Create Form -->
|
|
200
|
+
<div class="create-form">
|
|
201
|
+
<h3>Create New Subscription</h3>
|
|
202
|
+
|
|
203
|
+
<div v-if="createError" class="error">
|
|
204
|
+
Error: {{ createError.message }}
|
|
205
|
+
</div>
|
|
206
|
+
|
|
207
|
+
<div v-if="createSuccess" class="success">
|
|
208
|
+
Subscription created successfully!
|
|
209
|
+
</div>
|
|
210
|
+
|
|
211
|
+
<div class="form-row">
|
|
212
|
+
<label>Select Cameras:</label>
|
|
213
|
+
<select v-model="selectedCameras" multiple data-testid="camera-select">
|
|
214
|
+
<option v-for="camera in cameras" :key="camera.id" :value="camera.id">
|
|
215
|
+
{{ camera.name || camera.id }}
|
|
216
|
+
</option>
|
|
217
|
+
</select>
|
|
218
|
+
<small>Hold Ctrl/Cmd to select multiple</small>
|
|
219
|
+
</div>
|
|
220
|
+
|
|
221
|
+
<div class="form-row">
|
|
222
|
+
<label>Select Event Types:</label>
|
|
223
|
+
<select v-model="selectedEventTypes" multiple data-testid="event-type-select">
|
|
224
|
+
<option v-for="eventType in eventTypes" :key="eventType.type" :value="eventType.type">
|
|
225
|
+
{{ eventType.name }} ({{ eventType.type }})
|
|
226
|
+
</option>
|
|
227
|
+
</select>
|
|
228
|
+
<small>Hold Ctrl/Cmd to select multiple</small>
|
|
229
|
+
</div>
|
|
230
|
+
|
|
231
|
+
<button
|
|
232
|
+
@click="handleCreate"
|
|
233
|
+
:disabled="creating || selectedCameras.length === 0 || selectedEventTypes.length === 0"
|
|
234
|
+
data-testid="create-subscription-button"
|
|
235
|
+
>
|
|
236
|
+
{{ creating ? 'Creating...' : 'Create Subscription' }}
|
|
237
|
+
</button>
|
|
238
|
+
</div>
|
|
239
|
+
|
|
240
|
+
<!-- Subscriptions List -->
|
|
241
|
+
<div class="list-section">
|
|
242
|
+
<h3>Active Subscriptions</h3>
|
|
243
|
+
|
|
244
|
+
<div v-if="loading && subscriptions.length === 0" class="loading">
|
|
245
|
+
Loading subscriptions...
|
|
246
|
+
</div>
|
|
247
|
+
|
|
248
|
+
<div v-else-if="error" class="error">
|
|
249
|
+
Error: {{ error.message }}
|
|
250
|
+
</div>
|
|
251
|
+
|
|
252
|
+
<div v-else>
|
|
253
|
+
<table v-if="subscriptions.length > 0" data-testid="subscriptions-table">
|
|
254
|
+
<thead>
|
|
255
|
+
<tr>
|
|
256
|
+
<th>ID</th>
|
|
257
|
+
<th>Type</th>
|
|
258
|
+
<th>Lifecycle</th>
|
|
259
|
+
<th>TTL</th>
|
|
260
|
+
<th>Actions</th>
|
|
261
|
+
</tr>
|
|
262
|
+
</thead>
|
|
263
|
+
<tbody>
|
|
264
|
+
<tr v-for="sub in subscriptions" :key="sub.id">
|
|
265
|
+
<td class="id-cell" :title="sub.id">{{ sub.id.slice(0, 12) }}...</td>
|
|
266
|
+
<td>{{ getDeliveryType(sub) }}</td>
|
|
267
|
+
<td>{{ sub.subscriptionConfig?.lifeCycle || '-' }}</td>
|
|
268
|
+
<td>{{ sub.subscriptionConfig?.timeToLiveSeconds ? `${sub.subscriptionConfig.timeToLiveSeconds}s` : '-' }}</td>
|
|
269
|
+
<td class="actions">
|
|
270
|
+
<router-link
|
|
271
|
+
v-if="getSseUrl(sub)"
|
|
272
|
+
:to="{ path: '/live', query: { subscriptionId: sub.id } }"
|
|
273
|
+
>
|
|
274
|
+
<button class="secondary small">Listen</button>
|
|
275
|
+
</router-link>
|
|
276
|
+
<button
|
|
277
|
+
class="danger small"
|
|
278
|
+
@click="handleDelete(sub.id)"
|
|
279
|
+
:disabled="deleting === sub.id"
|
|
280
|
+
:data-testid="`delete-${sub.id}`"
|
|
281
|
+
>
|
|
282
|
+
{{ deleting === sub.id ? 'Deleting...' : 'Delete' }}
|
|
283
|
+
</button>
|
|
284
|
+
</td>
|
|
285
|
+
</tr>
|
|
286
|
+
</tbody>
|
|
287
|
+
</table>
|
|
288
|
+
|
|
289
|
+
<p v-else class="no-data">No subscriptions found. Create one above to get started.</p>
|
|
290
|
+
|
|
291
|
+
<div v-if="hasNextPage" class="pagination">
|
|
292
|
+
<button @click="fetchNextPage" :disabled="loading">
|
|
293
|
+
{{ loading ? 'Loading...' : 'Load More' }}
|
|
294
|
+
</button>
|
|
295
|
+
</div>
|
|
296
|
+
</div>
|
|
297
|
+
</div>
|
|
298
|
+
</div>
|
|
299
|
+
</template>
|
|
300
|
+
|
|
301
|
+
<style scoped>
|
|
302
|
+
.subscriptions {
|
|
303
|
+
max-width: 900px;
|
|
304
|
+
margin: 0 auto;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.header {
|
|
308
|
+
display: flex;
|
|
309
|
+
justify-content: space-between;
|
|
310
|
+
align-items: center;
|
|
311
|
+
margin-bottom: 20px;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.create-form {
|
|
315
|
+
background: #f8f9fa;
|
|
316
|
+
padding: 20px;
|
|
317
|
+
border-radius: 8px;
|
|
318
|
+
margin-bottom: 30px;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.create-form h3 {
|
|
322
|
+
margin-bottom: 15px;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.form-row {
|
|
326
|
+
margin-bottom: 15px;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
.form-row label {
|
|
330
|
+
display: block;
|
|
331
|
+
margin-bottom: 5px;
|
|
332
|
+
font-weight: 500;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.form-row select {
|
|
336
|
+
width: 100%;
|
|
337
|
+
min-height: 100px;
|
|
338
|
+
padding: 8px;
|
|
339
|
+
border: 1px solid #ddd;
|
|
340
|
+
border-radius: 4px;
|
|
341
|
+
font-size: 14px;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
.form-row small {
|
|
345
|
+
display: block;
|
|
346
|
+
margin-top: 5px;
|
|
347
|
+
color: #666;
|
|
348
|
+
font-size: 12px;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.list-section {
|
|
352
|
+
margin-top: 20px;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
.list-section h3 {
|
|
356
|
+
margin-bottom: 15px;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
table {
|
|
360
|
+
width: 100%;
|
|
361
|
+
border-collapse: collapse;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
th,
|
|
365
|
+
td {
|
|
366
|
+
padding: 12px;
|
|
367
|
+
text-align: left;
|
|
368
|
+
border-bottom: 1px solid #eee;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
th {
|
|
372
|
+
background: #f5f5f5;
|
|
373
|
+
font-weight: 600;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
.id-cell {
|
|
377
|
+
font-family: monospace;
|
|
378
|
+
font-size: 12px;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
.actions {
|
|
382
|
+
display: flex;
|
|
383
|
+
gap: 8px;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
button.small {
|
|
387
|
+
padding: 6px 12px;
|
|
388
|
+
font-size: 12px;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
.pagination {
|
|
392
|
+
margin-top: 20px;
|
|
393
|
+
text-align: center;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
.no-data {
|
|
397
|
+
color: #666;
|
|
398
|
+
font-style: italic;
|
|
399
|
+
padding: 20px;
|
|
400
|
+
text-align: center;
|
|
401
|
+
}
|
|
402
|
+
</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
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"moduleResolution": "bundler",
|
|
9
|
+
"allowImportingTsExtensions": true,
|
|
10
|
+
"resolveJsonModule": true,
|
|
11
|
+
"isolatedModules": true,
|
|
12
|
+
"noEmit": true,
|
|
13
|
+
"jsx": "preserve",
|
|
14
|
+
"strict": true,
|
|
15
|
+
"noUnusedLocals": true,
|
|
16
|
+
"noUnusedParameters": true,
|
|
17
|
+
"noFallthroughCasesInSwitch": true
|
|
18
|
+
},
|
|
19
|
+
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"],
|
|
20
|
+
"references": [{ "path": "./tsconfig.node.json" }]
|
|
21
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { defineConfig } from 'vite'
|
|
2
|
+
import vue from '@vitejs/plugin-vue'
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
plugins: [vue()],
|
|
6
|
+
server: {
|
|
7
|
+
// IMPORTANT: Must use 127.0.0.1:3333 for EEN OAuth callback
|
|
8
|
+
// The EEN Identity Provider only permits this specific redirect URI
|
|
9
|
+
host: '127.0.0.1',
|
|
10
|
+
port: 3333
|
|
11
|
+
}
|
|
12
|
+
})
|