@nitra/vite-boot 3.8.1 → 4.0.1
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/package.json +2 -1
- package/src/apollo.js +85 -88
- package/src/login.js +3 -2
- package/src/router.js +1 -0
- package/src/token.js +5 -9
- package/src/user.js +1 -0
- package/src/vite-env.d.ts +5 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nitra/vite-boot",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.1",
|
|
4
4
|
"description": "Vite boot",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@apollo/client": "3.*",
|
|
31
31
|
"@nitra/jwt-decode": "^1.2.0",
|
|
32
|
+
"@nitra/log": "^0.0.3",
|
|
32
33
|
"@sentry/tracing": "^7.120.4",
|
|
33
34
|
"@sentry/vue": "^10.10.0",
|
|
34
35
|
"graphql-ws": "^6.0.6",
|
package/src/apollo.js
CHANGED
|
@@ -1,110 +1,107 @@
|
|
|
1
|
-
import { ApolloClient, InMemoryCache, from } from '@apollo/client
|
|
2
|
-
import {
|
|
3
|
-
import { createClient } from 'graphql-ws'
|
|
1
|
+
import { ApolloClient, InMemoryCache, createHttpLink, from, split } from '@apollo/client'
|
|
2
|
+
import { setContext } from '@apollo/client/link/context'
|
|
4
3
|
import { GraphQLWsLink } from '@apollo/client/link/subscriptions'
|
|
5
|
-
import {
|
|
4
|
+
import { getMainDefinition } from '@apollo/client/utilities'
|
|
5
|
+
import { log } from '@nitra/log'
|
|
6
|
+
import { createClient } from 'graphql-ws'
|
|
7
|
+
import { checkToken, getToken } from './token.js'
|
|
6
8
|
import { user } from './user.js'
|
|
7
9
|
|
|
10
|
+
const httpLink = createHttpLink({
|
|
11
|
+
uri: `${import.meta.env.VITE_HASURA_PROTOCOL === 'wss' ? 'https' : 'http'}://${
|
|
12
|
+
import.meta.env.VITE_HASURA_HOST
|
|
13
|
+
}/v1/graphql`
|
|
14
|
+
})
|
|
15
|
+
|
|
8
16
|
/**
|
|
9
|
-
*
|
|
10
|
-
* @
|
|
17
|
+
* Ініціалізуємо логаут
|
|
18
|
+
* @returns {void}
|
|
11
19
|
*/
|
|
12
|
-
function
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
20
|
+
export function logout() {
|
|
21
|
+
const logoutUrl = `${import.meta.env.BASE_URL}logout`
|
|
22
|
+
// Якщо не на сторінці логауту, то перенаправляємо на сторінку логауту
|
|
23
|
+
if (globalThis.location.pathname !== logoutUrl) {
|
|
24
|
+
globalThis.location.href = logoutUrl
|
|
16
25
|
}
|
|
26
|
+
}
|
|
17
27
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
restart = () => {
|
|
26
|
-
if (socket.readyState === WebSocket.OPEN) {
|
|
27
|
-
// if the socket is still open for the restart, do the restart
|
|
28
|
-
socket.close(4205, 'Client Restart')
|
|
29
|
-
} else {
|
|
30
|
-
// otherwise the socket might've closed, indicate that you want
|
|
31
|
-
// a restart on the next opened event
|
|
32
|
-
restartRequested = true
|
|
33
|
-
}
|
|
34
|
-
}
|
|
28
|
+
/**
|
|
29
|
+
*
|
|
30
|
+
* @param headers
|
|
31
|
+
* @returns {object}
|
|
32
|
+
*/
|
|
33
|
+
function prepareHeaders(headers = {}) {
|
|
34
|
+
const token = getToken()
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
})
|
|
36
|
+
if (!token) {
|
|
37
|
+
logout()
|
|
38
|
+
}
|
|
39
|
+
const check = checkToken(token)
|
|
44
40
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
41
|
+
// Якщо роль не підходить, то перенаправляємо на сторінку логауту
|
|
42
|
+
if (check.result === 'broken-role') {
|
|
43
|
+
logout()
|
|
48
44
|
}
|
|
49
|
-
}
|
|
50
45
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
connectionParams: () => {
|
|
55
|
-
const token = getToken()
|
|
46
|
+
if (check.result !== 'ok' || !user.role) {
|
|
47
|
+
logout()
|
|
48
|
+
}
|
|
56
49
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (check.result === 'ok' && user.role) {
|
|
60
|
-
const headers = {}
|
|
61
|
-
// Якщо є токен - додаємо його в заголовки
|
|
62
|
-
headers.Authorization = `Bearer ${token}`
|
|
63
|
-
headers['x-hasura-role'] = user.role
|
|
50
|
+
headers.Authorization = `Bearer ${token}`
|
|
51
|
+
headers['x-hasura-role'] = user.role
|
|
64
52
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
}
|
|
53
|
+
return { headers }
|
|
54
|
+
}
|
|
69
55
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
56
|
+
// Додаємо токен і роль до кожного HTTP-запиту
|
|
57
|
+
const authLink = setContext((_, { headers }) => {
|
|
58
|
+
return prepareHeaders({ ...headers })
|
|
73
59
|
})
|
|
74
60
|
|
|
75
|
-
const wsLink = new GraphQLWsLink(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
61
|
+
export const wsLink = new GraphQLWsLink(
|
|
62
|
+
createClient({
|
|
63
|
+
url: `${import.meta.env.VITE_HASURA_PROTOCOL}://${import.meta.env.VITE_HASURA_HOST}/v1/graphql`,
|
|
64
|
+
connectionParams: () => {
|
|
65
|
+
return prepareHeaders()
|
|
66
|
+
},
|
|
67
|
+
// Обробник успішного підключення
|
|
68
|
+
on: {
|
|
69
|
+
// Обробник помилок підключення
|
|
70
|
+
error: error => {
|
|
71
|
+
log.error('WebSocket помилка:', error)
|
|
72
|
+
},
|
|
83
73
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
// Чистимо токен
|
|
88
|
-
refreshToken()
|
|
74
|
+
// Обробник закриття з'єднання
|
|
75
|
+
closed: event => {
|
|
76
|
+
log.info('WebSocket закрито:', event.code, event.reason)
|
|
89
77
|
|
|
90
|
-
|
|
91
|
-
|
|
78
|
+
// Коди помилок аутентифікації
|
|
79
|
+
if (event.code === 4401) {
|
|
80
|
+
log.error('Неавторизований: неправильний токен')
|
|
81
|
+
} else if (event.code === 4403) {
|
|
82
|
+
log.error('Заборонено: недостатньо прав')
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
lazy: true, // відкрити сокет тільки при першій subscription
|
|
87
|
+
lazyCloseTimeout: 15_000, // дочекатися 15 с після останньої відписки і лише тоді закрити сокет
|
|
88
|
+
keepAlive: 10_000, // ping кожні 10 с, щоб тримати з’єднання живим
|
|
89
|
+
retryAttempts: Infinity // автоперепідключення без ліміту
|
|
90
|
+
})
|
|
91
|
+
)
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
93
|
+
// Розділяємо трафік: subscription -> WS, інше -> HTTP
|
|
94
|
+
const link = split(
|
|
95
|
+
({ query }) => {
|
|
96
|
+
const def = getMainDefinition(query)
|
|
97
|
+
return def.kind === 'OperationDefinition' && def.operation === 'subscription'
|
|
98
|
+
},
|
|
99
|
+
wsLink,
|
|
100
|
+
from([authLink, httpLink])
|
|
101
|
+
)
|
|
101
102
|
|
|
103
|
+
// Ініціалізуємо клієнт
|
|
102
104
|
export const apolloClient = new ApolloClient({
|
|
103
|
-
link
|
|
104
|
-
cache: new InMemoryCache()
|
|
105
|
-
defaultOptions: {
|
|
106
|
-
watchQuery: {
|
|
107
|
-
fetchPolicy: 'cache-and-network'
|
|
108
|
-
}
|
|
109
|
-
}
|
|
105
|
+
link,
|
|
106
|
+
cache: new InMemoryCache()
|
|
110
107
|
})
|
package/src/login.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { checkToken, getToken } from './token.js'
|
|
2
2
|
import { setUser } from './user.js'
|
|
3
3
|
// import { router } from './router.js'
|
|
4
4
|
// Використовуємо глобальну версію
|
|
@@ -6,6 +6,7 @@ import { setUser } from './user.js'
|
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Автоматичний вхід користувача
|
|
9
|
+
* @returns {Promise<void>}
|
|
9
10
|
*/
|
|
10
11
|
export async function autoLogin() {
|
|
11
12
|
const token = getToken()
|
|
@@ -34,5 +35,5 @@ export async function defaultLogin(decoded, raw) {
|
|
|
34
35
|
setUser(decoded)
|
|
35
36
|
// @ts-ignore
|
|
36
37
|
const route = sessionStorage.getItem('url-before-logout') || import.meta.env.BASE_URL
|
|
37
|
-
|
|
38
|
+
globalThis.location.href = route // router.push(route)
|
|
38
39
|
}
|
package/src/router.js
CHANGED
package/src/token.js
CHANGED
|
@@ -14,7 +14,6 @@ const allowedRoles = import.meta.env.VITE_HASURA_ROLE.split(',')
|
|
|
14
14
|
*/
|
|
15
15
|
export function checkToken(token) {
|
|
16
16
|
const decoded = jwtDecode(token)
|
|
17
|
-
|
|
18
17
|
if (!decoded || !decoded['https://hasura.io/jwt/claims']) {
|
|
19
18
|
cleanToken()
|
|
20
19
|
return { result: 'cleaned' }
|
|
@@ -36,6 +35,7 @@ export function checkToken(token) {
|
|
|
36
35
|
|
|
37
36
|
/**
|
|
38
37
|
* Токен з кукі
|
|
38
|
+
* @returns {string} токен
|
|
39
39
|
*/
|
|
40
40
|
export function getToken() {
|
|
41
41
|
// інакше з кукі
|
|
@@ -62,7 +62,7 @@ export function cleanToken() {
|
|
|
62
62
|
localStorage.removeItem('__session')
|
|
63
63
|
|
|
64
64
|
// @ts-ignore
|
|
65
|
-
document.cookie = `__session=; Max-Age=0; path=/; domain=${import.meta.env.VITE_DOMAIN}`
|
|
65
|
+
document.cookie = `__session=; Max-Age=0; path=/; domain=${import.meta.env.VITE_DOMAIN}`
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
/**
|
|
@@ -96,9 +96,6 @@ export async function refreshToken() {
|
|
|
96
96
|
return
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
// Прибираємо токен, щоб інші паралельні JWTExpired не виконувались
|
|
100
|
-
// cleanToken()
|
|
101
|
-
|
|
102
99
|
const response = await fetch('/auth/refresh-token', {
|
|
103
100
|
method: 'POST',
|
|
104
101
|
headers: {
|
|
@@ -111,23 +108,22 @@ export async function refreshToken() {
|
|
|
111
108
|
|
|
112
109
|
// Якщо НЕ успішно отримали токен
|
|
113
110
|
if (!result.token) {
|
|
114
|
-
// cleanToken()
|
|
115
111
|
return
|
|
116
112
|
}
|
|
117
113
|
|
|
118
114
|
const check = checkToken(result.token)
|
|
119
115
|
if (check.result !== 'ok') {
|
|
120
|
-
// cleanToken()
|
|
121
116
|
return
|
|
122
117
|
}
|
|
123
118
|
|
|
124
119
|
// Якщо запущений аполо
|
|
125
120
|
// допускаємо що ми всередині прикладення
|
|
126
|
-
if (
|
|
121
|
+
if (globalThis.location.pathname !== '/login' && apolloClient) {
|
|
127
122
|
setUser(check.decoded)
|
|
128
123
|
|
|
129
124
|
// перезавантажуємо щоб він вступив в дію
|
|
130
|
-
wsClient.restart()
|
|
125
|
+
// wsClient.restart()
|
|
126
|
+
wsClient.close()
|
|
131
127
|
} else {
|
|
132
128
|
await defaultLogin(check.decoded, result.token)
|
|
133
129
|
}
|
package/src/user.js
CHANGED