@nitra/vite-boot 3.8.1 → 4.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,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/vite-boot",
3
- "version": "3.8.1",
3
+ "version": "4.0.0",
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/core'
2
- import { onError } from '@apollo/client/link/error'
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 { checkToken, refreshToken, getToken } from './token.js'
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
- * @param options
17
+ * Ініціалізуємо логаут
18
+ * @returns {void}
11
19
  */
12
- function createRestartableClient(options) {
13
- let restartRequested = false
14
- let restart = () => {
15
- restartRequested = true
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
- const client = createClient({
19
- ...options,
20
- on: {
21
- ...options.on,
22
- opened: socket => {
23
- options.on?.opened?.(socket)
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
- // just in case you were eager to restart
37
- if (restartRequested) {
38
- restartRequested = false
39
- restart()
40
- }
41
- }
42
- }
43
- })
36
+ if (!token) {
37
+ logout()
38
+ }
39
+ const check = checkToken(token)
44
40
 
45
- return {
46
- ...client,
47
- restart: () => restart()
41
+ // Якщо роль не підходить, то перенаправляємо на сторінку логауту
42
+ if (check.result === 'broken-role') {
43
+ logout()
48
44
  }
49
- }
50
45
 
51
- export let connectionParam = {}
52
- export const wsClient = createRestartableClient({
53
- url: `${import.meta.env.VITE_HASURA_PROTOCOL}://${import.meta.env.VITE_HASURA_HOST}/v1/graphql`,
54
- connectionParams: () => {
55
- const token = getToken()
46
+ if (check.result !== 'ok' || !user.role) {
47
+ logout()
48
+ }
56
49
 
57
- if (token) {
58
- const check = checkToken(token)
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
- connectionParam = { headers }
66
- return connectionParam
67
- }
68
- }
53
+ return { headers }
54
+ }
69
55
 
70
- connectionParam = {}
71
- return connectionParam
72
- }
56
+ // Додаємо токен і роль до кожного HTTP-запиту
57
+ const authLink = setContext((_, { headers }) => {
58
+ return prepareHeaders({ ...headers })
73
59
  })
74
60
 
75
- const wsLink = new GraphQLWsLink(wsClient)
76
-
77
- const errorLink = onError(({ graphQLErrors, networkError }) => {
78
- if (graphQLErrors) {
79
- for (const err of graphQLErrors) {
80
- console.error('graphQLErrors:', err)
81
- }
82
- }
61
+ 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
- if (networkError) {
85
- if (networkError?.message?.match('JWTExpired')) {
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
- // } else if (networkError?.message === `Invalid message 'type' property "connection_error"`) {
94
- // // Роль не підходить хашурі
95
- // router.push('/logout')
96
- } else {
97
- console.log(`[Network error]:`, networkError?.message, networkError)
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: from([errorLink, wsLink]),
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 { getToken, checkToken } from './token.js'
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
- window.location.href = route // router.push(route)
38
+ globalThis.location.href = route // router.push(route)
38
39
  }
package/src/router.js CHANGED
@@ -14,6 +14,7 @@ export function setRouter(r) {
14
14
  /**
15
15
  *
16
16
  * @param to
17
+ * @returns {boolean}
17
18
  */
18
19
  export function canUserAccess(to) {
19
20
  // Якщо сторінка без обмеження по ролям то дозволяємо завжди
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}` // eslint-disable-line unicorn/no-document-cookie
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,21 @@ 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 (window.location.pathname !== '/login' && apolloClient) {
121
+ if (globalThis.location.pathname !== '/login' && apolloClient) {
127
122
  setUser(check.decoded)
128
123
 
129
124
  // перезавантажуємо щоб він вступив в дію
130
- wsClient.restart()
125
+ // wsClient.restart()
131
126
  } else {
132
127
  await defaultLogin(check.decoded, result.token)
133
128
  }
package/src/user.js CHANGED
@@ -26,6 +26,7 @@ export function setUser(u) {
26
26
  *
27
27
  * @param a
28
28
  * @param b
29
+ * @returns {string[]}
29
30
  */
30
31
  function intersection(a, b) {
31
32
  const setA = new Set(a)
@@ -0,0 +1,5 @@
1
+ /// <reference types="vite/client" />
2
+
3
+ interface ImportMeta {
4
+ readonly env: ImportMetaEnv
5
+ }