@nitra/vite-boot 4.1.3 → 5.0.0

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 CHANGED
@@ -1,13 +1,12 @@
1
1
  {
2
2
  "name": "@nitra/vite-boot",
3
- "version": "4.1.3",
3
+ "version": "5.0.0",
4
4
  "description": "Vite boot",
5
5
  "type": "module",
6
6
  "exports": {
7
7
  "./apollo": "./src/apollo.js",
8
8
  "./pinia": "./src/pinia.js",
9
9
  "./token": "./src/token.js",
10
- "./login": "./src/login.js",
11
10
  "./user": "./src/user.js",
12
11
  "./router": "./src/router.js"
13
12
  },
@@ -26,9 +25,9 @@
26
25
  "src"
27
26
  ],
28
27
  "dependencies": {
29
- "@apollo/client": "^4.0.6",
28
+ "@apollo/client": "^4.0.7",
30
29
  "@nitra/jwt-decode": "^1.2.0",
31
- "@vue/apollo-composable": "^4.2.2",
30
+ "@vue3-apollo/core": "^1.3.1",
32
31
  "graphql-ws": "^6.0.6",
33
32
  "pinia": "^3.0.3",
34
33
  "pinia-plugin-persistedstate": "^4.5.0",
package/src/apollo.js CHANGED
@@ -1,15 +1,26 @@
1
- import { ApolloClient, InMemoryCache, createHttpLink, from, split } from '@apollo/client'
2
- import { setContext } from '@apollo/client/link/context'
1
+ import { ApolloClient, ApolloLink, HttpLink, InMemoryCache } from '@apollo/client/core'
3
2
  import { GraphQLWsLink } from '@apollo/client/link/subscriptions'
4
3
  import { getMainDefinition } from '@apollo/client/utilities'
5
4
  import { createClient } from 'graphql-ws'
6
- import { checkToken, getToken } from './token.js'
7
- import { user } from './user.js'
8
-
5
+ import { checkedToken, cleanToken, user } from './user.js'
9
6
  export { gql } from '@apollo/client/core'
10
- export { provideApolloClient, useMutation, useQuery, useSubscription } from '@vue/apollo-composable'
7
+ export { apolloPlugin, useApolloClient, useMutation, useQuery, useSubscription } from '@vue3-apollo/core'
8
+
9
+ function getAuthHeaders() {
10
+ // Якщо є токен - додаємо його в заголовки
11
+ if (checkedToken) {
12
+ const headers = {}
13
+
14
+ headers.Authorization = `Bearer ${checkedToken}`
15
+ headers['x-hasura-role'] = user.role
16
+
17
+ return headers
18
+ }
19
+
20
+ return {}
21
+ }
11
22
 
12
- const httpLink = createHttpLink({
23
+ const httpLink = new HttpLink({
13
24
  uri: `${import.meta.env.VITE_HASURA_PROTOCOL === 'wss' ? 'https' : 'http'}://${
14
25
  import.meta.env.VITE_HASURA_HOST
15
26
  }/v1/graphql`
@@ -20,6 +31,9 @@ const httpLink = createHttpLink({
20
31
  * @returns {void}
21
32
  */
22
33
  export function logout() {
34
+ cleanToken()
35
+ // Очищаємо Apollo Client кеш і закриваємо зʼєднання
36
+ resetApolloClient()
23
37
  const logoutUrl = `${import.meta.env.BASE_URL}logout`
24
38
  // Якщо не на сторінці логауту, то перенаправляємо на сторінку логауту
25
39
  if (globalThis.location.pathname !== logoutUrl) {
@@ -32,47 +46,34 @@ export function logout() {
32
46
  * @param {Record<string, string>} headers - Початкові заголовки запиту
33
47
  * @returns {{headers: Record<string, string>}} - Обʼєкт з підготовленими заголовками
34
48
  */
35
- function prepareHeaders(headers = {}) {
36
- const token = getToken()
37
-
38
- if (!token) {
39
- logout()
40
- }
41
- const check = checkToken(token)
42
-
43
- // Якщо роль не підходить, то перенаправляємо на сторінку логауту
44
- if (check.result === 'broken-role') {
45
- logout()
46
- }
47
-
48
- if (check.result !== 'ok' || !user.role) {
49
- logout()
50
- }
51
49
 
52
- headers.Authorization = `Bearer ${token}`
53
- headers['x-hasura-role'] = user.role
54
-
55
- return { headers }
56
- }
57
-
58
- // Додаємо токен і роль до кожного HTTP-запиту
59
- const authLink = setContext((_, { headers }) => {
60
- return prepareHeaders({ ...headers })
50
+ // Додаємо токен і роль до кожного HTTP-запиту за допомогою ApolloLink
51
+ const authLink = new ApolloLink((operation, forward) => {
52
+ const existingHeaders = operation.getContext().headers || {}
53
+ operation.setContext({
54
+ headers: {
55
+ ...existingHeaders,
56
+ ...getAuthHeaders()
57
+ }
58
+ })
59
+ return forward(operation)
61
60
  })
62
61
 
63
62
  export const wsClient = createClient({
64
63
  url: `${import.meta.env.VITE_HASURA_PROTOCOL}://${import.meta.env.VITE_HASURA_HOST}/v1/graphql`,
65
- connectionParams: () => {
66
- return prepareHeaders()
67
- },
64
+ connectionParams: () => ({
65
+ headers: getAuthHeaders()
66
+ }),
68
67
  // Обробник успішного підключення
69
68
  on: {
70
69
  // Обробник помилок підключення
70
+ /** @param {unknown} error - Помилка WebSocket з'єднання */
71
71
  error: error => {
72
72
  console.error('WebSocket помилка:', error)
73
73
  },
74
74
 
75
75
  // Обробник закриття з'єднання
76
+ /** @param {{ code: number, reason: string }} event - Подія закриття WebSocket */
76
77
  closed: event => {
77
78
  console.info('WebSocket закрито:', event.code, event.reason)
78
79
 
@@ -93,13 +94,13 @@ export const wsClient = createClient({
93
94
  const wsLink = new GraphQLWsLink(wsClient)
94
95
 
95
96
  // Розділяємо трафік: subscription -> WS, інше -> HTTP
96
- const link = split(
97
+ const link = ApolloLink.split(
97
98
  ({ query }) => {
98
99
  const def = getMainDefinition(query)
99
100
  return def.kind === 'OperationDefinition' && def.operation === 'subscription'
100
101
  },
101
102
  wsLink,
102
- from([authLink, httpLink])
103
+ ApolloLink.from([authLink, httpLink])
103
104
  )
104
105
 
105
106
  // Ініціалізуємо клієнт
@@ -107,3 +108,20 @@ export const apolloClient = new ApolloClient({
107
108
  link,
108
109
  cache: new InMemoryCache()
109
110
  })
111
+ /**
112
+ * Повне скидання Apollo Client (для критичних випадків)
113
+ */
114
+ export function resetApolloClient() {
115
+ if (apolloClient) {
116
+ // Очищаємо кеш
117
+ apolloClient.clearStore()
118
+
119
+ // Закриваємо всі активні запити
120
+ apolloClient.stop()
121
+ }
122
+
123
+ // Закриваємо WebSocket з'єднання
124
+ if (wsClient && wsClient.dispose) {
125
+ wsClient.dispose()
126
+ }
127
+ }
package/src/router.js CHANGED
@@ -36,7 +36,7 @@ export function canUserAccess(to) {
36
36
  if (!user.role) {
37
37
  const check = checkToken(token)
38
38
  if (check.result === 'ok') {
39
- setUser(check.decoded)
39
+ setUser(check.decoded, token)
40
40
  } else {
41
41
  return false
42
42
  }
@@ -76,7 +76,7 @@ export function canUserAccessMetaRoles(to) {
76
76
  if (!user.role) {
77
77
  const check = checkToken(token)
78
78
  if (check.result === 'ok') {
79
- setUser(check.decoded)
79
+ setUser(check.decoded, token)
80
80
  } else {
81
81
  return false
82
82
  }
package/src/token.js CHANGED
@@ -1,7 +1,5 @@
1
1
  import jwtDecode from '@nitra/jwt-decode'
2
- import { apolloClient, wsClient } from './apollo.js'
3
- import { defaultLogin } from './login.js'
4
- import { setUser, unsetUser } from './user.js'
2
+ import { cleanToken, setUser, unsetUser } from './user.js'
5
3
 
6
4
  // @ts-ignore
7
5
  const allowedRoles = import.meta.env.VITE_HASURA_ROLE.split(',')
@@ -12,6 +10,7 @@ const allowedRoles = import.meta.env.VITE_HASURA_ROLE.split(',')
12
10
  * @returns {{result: 'ok'|'expired'|'cleaned'|'broken-role', decoded?: object}} - Результат перевірки
13
11
  */
14
12
  export function checkToken(token) {
13
+ /** @type {{ exp?: number, [k: string]: any }} */
15
14
  const decoded = jwtDecode(token)
16
15
  if (!decoded || !decoded['https://hasura.io/jwt/claims']) {
17
16
  cleanToken()
@@ -19,67 +18,69 @@ export function checkToken(token) {
19
18
  }
20
19
 
21
20
  if (!decoded['https://hasura.io/jwt/claims']['x-hasura-allowed-roles'].some(x => allowedRoles.includes(x))) {
21
+ unsetUser()
22
22
  // Виходимо
23
23
  return { result: 'broken-role' }
24
24
  }
25
25
 
26
26
  const isExpired = !decoded || Math.floor(Date.now() / 1000) > decoded.exp
27
27
  if (isExpired) {
28
+ unsetUser()
28
29
  refreshToken()
29
30
  return { result: 'expired' }
30
31
  }
31
32
 
33
+ setUser(decoded, token)
32
34
  return { result: 'ok', decoded }
33
35
  }
34
36
 
35
37
  /**
36
- * Токен з кукі
37
- * @returns {string} токен
38
+ * Вхід або на попередню або на кореневу сторінку
39
+ * @param {object} decoded - Розкодований JWT з Hasura claims
40
+ * @param {string} raw - JWT як рядок (для localStorage)
41
+ * @returns {void}
38
42
  */
39
- export function getToken() {
40
- // інакше з кукі
41
- const cookieObj = new URLSearchParams(document.cookie.replaceAll('; ', '&'))
42
- const token = cookieObj.get('__session')
43
- if (token) {
44
- return token
45
- }
43
+ export function defaultLogin(decoded, raw) {
44
+ // Якщо задано що токен з localStorage
45
+ localStorage.setItem('__session', raw)
46
46
 
47
- // Якщо задано що токен беремо з localStorage
48
- return localStorage.getItem('__session')
47
+ setUser(decoded, raw)
48
+ // @ts-ignore
49
+ const route = sessionStorage.getItem('url-before-logout') || import.meta.env.BASE_URL
50
+ globalThis.location.href = route // router.push(route)
49
51
  }
50
52
 
51
53
  /**
52
- * Переводимо в анонімного користувача
54
+ * Автоматичний вхід користувача
55
+ * @returns {Promise<void>}
53
56
  */
54
- export function cleanToken() {
55
- unsetUser()
56
-
57
- // Очищаємо Apollo Client кеш
58
- resetApolloClient()
57
+ export function autoLogin() {
58
+ const token = getToken()
59
+ if (!token) {
60
+ return
61
+ }
59
62
 
60
- // видаляємо з localStorage
61
- localStorage.removeItem('__session')
63
+ const check = checkToken(token)
64
+ if (check.result !== 'ok') {
65
+ return
66
+ }
62
67
 
63
- // @ts-ignore
64
- document.cookie = `__session=; Max-Age=0; path=/; domain=${import.meta.env.VITE_DOMAIN}`
68
+ defaultLogin(check.decoded, token)
65
69
  }
66
-
67
70
  /**
68
- * Повне скидання Apollo Client (для критичних випадків)
71
+ * Токен з кукі
72
+ * @returns {string} токен
69
73
  */
70
- export function resetApolloClient() {
71
- if (apolloClient) {
72
- // Очищаємо кеш
73
- apolloClient.clearStore()
74
-
75
- // Закриваємо всі активні запити
76
- apolloClient.stop()
74
+ export function getToken() {
75
+ // інакше з кукі
76
+ const cookieObj = new URLSearchParams(document.cookie.replaceAll('; ', '&'))
77
+ const token = cookieObj.get('__session')
78
+ if (token) {
79
+ return token
77
80
  }
78
81
 
79
- // Закриваємо WebSocket з'єднання
80
- if (wsClient && wsClient.dispose) {
81
- wsClient.dispose()
82
- }
82
+ // Якщо задано що токен беремо з localStorage
83
+ return localStorage.getItem('__session')
83
84
  }
84
85
 
85
86
  /**
@@ -117,14 +118,14 @@ export async function refreshToken() {
117
118
 
118
119
  // Якщо запущений аполо
119
120
  // допускаємо що ми всередині прикладення
120
- if (globalThis.location.pathname !== '/login' && apolloClient) {
121
+ if (globalThis.location.pathname === '/login') {
122
+ await defaultLogin(check.decoded, result.token)
123
+ } else {
121
124
  setUser(check.decoded)
122
125
 
123
126
  // перезавантажуємо щоб він вступив в дію
124
127
  // wsClient.restart()
125
- wsClient.dispose()
126
- } else {
127
- await defaultLogin(check.decoded, result.token)
128
+ // wsClient.dispose()
128
129
  }
129
130
  } catch (error) {
130
131
  console.error('Error:', error)
package/src/user.js CHANGED
@@ -1,5 +1,11 @@
1
1
  const allowedRoles = import.meta.env.VITE_HASURA_ROLE.split(',')
2
2
 
3
+ // Декодована версія токена, яка пройшла перевірку, на те що вона:
4
+ // - має /hasura.io/jwt/claims
5
+ // - має дозволені ролі
6
+ // - не застарів
7
+ export let checkedToken
8
+
3
9
  export let user = {}
4
10
 
5
11
  /**
@@ -7,18 +13,21 @@ export let user = {}
7
13
  */
8
14
  export function unsetUser() {
9
15
  user = {}
16
+ checkedToken = null
10
17
  }
11
18
 
12
19
  /**
13
20
  * Встановлює дані користувача з розкодованого JWT
14
- * @param {object} u - Обʼєкт з Hasura claims розкодованого токена
21
+ * @param {object} decoded - Обʼєкт з Hasura claims розкодованого токена
22
+ * @param {String} raw - Raw токен
15
23
  */
16
- export function setUser(u) {
17
- user = u
24
+ export function setUser(decoded, raw = null) {
25
+ user = decoded
26
+ checkedToken = raw
18
27
 
19
- user.login = u['https://hasura.io/jwt/claims']['x-hasura-user-id']
28
+ user.login = decoded['https://hasura.io/jwt/claims']['x-hasura-user-id']
20
29
  // Роль за умовчанням
21
- const intersect = intersection(allowedRoles, u['https://hasura.io/jwt/claims']['x-hasura-allowed-roles'])
30
+ const intersect = intersection(allowedRoles, decoded['https://hasura.io/jwt/claims']['x-hasura-allowed-roles'])
22
31
  user.role = intersect[0]
23
32
  }
24
33
 
@@ -32,3 +41,16 @@ function intersection(a, b) {
32
41
  const setA = new Set(a)
33
42
  return b.filter(value => setA.has(value))
34
43
  }
44
+
45
+ /**
46
+ * Переводимо в анонімного користувача
47
+ */
48
+ export function cleanToken() {
49
+ unsetUser()
50
+
51
+ // видаляємо з localStorage
52
+ localStorage.removeItem('__session')
53
+
54
+ // @ts-ignore
55
+ document.cookie = `__session=; Max-Age=0; path=/; domain=${import.meta.env.VITE_DOMAIN}`
56
+ }
@@ -1,20 +0,0 @@
1
- // Apollo Client v4 compatibility shim for @vue/apollo-composable
2
- export class ApolloError extends Error {
3
- constructor(opts = {}) {
4
- super(opts.errorMessage || 'Apollo Client Error')
5
- this.name = 'ApolloError'
6
- this.graphQLErrors = opts.graphQLErrors ?? []
7
- this.clientErrors = opts.clientErrors ?? []
8
- this.networkError = opts.networkError
9
- this.extraInfo = opts.extraInfo
10
- }
11
- }
12
-
13
- /**
14
- *
15
- * @param err
16
- */
17
- export function isApolloError(err) {
18
- return err instanceof ApolloError
19
- }
20
- // export * from '@apollo/client/core' // реекспорт всього
package/src/login.js DELETED
@@ -1,39 +0,0 @@
1
- import { checkToken, getToken } from './token.js'
2
- import { setUser } from './user.js'
3
- // import { router } from './router.js'
4
- // Використовуємо глобальну версію
5
- // import { router } from '@nitra/vite-boot/router'
6
-
7
- /**
8
- * Автоматичний вхід користувача
9
- * @returns {Promise<void>}
10
- */
11
- export function autoLogin() {
12
- const token = getToken()
13
- if (!token) {
14
- return
15
- }
16
-
17
- const check = checkToken(token)
18
- if (check.result !== 'ok') {
19
- return
20
- }
21
-
22
- defaultLogin(check.decoded, token)
23
- }
24
-
25
- /**
26
- * Вхід або на попередню або на кореневу сторінку
27
- * @param {object} decoded - Розкодований JWT з Hasura claims
28
- * @param {string} raw - JWT як рядок (для localStorage)
29
- * @returns {void}
30
- */
31
- export function defaultLogin(decoded, raw) {
32
- // Якщо задано що токен з localStorage
33
- localStorage.setItem('__session', raw)
34
-
35
- setUser(decoded)
36
- // @ts-ignore
37
- const route = sessionStorage.getItem('url-before-logout') || import.meta.env.BASE_URL
38
- globalThis.location.href = route // router.push(route)
39
- }