@kuzntsv/uikit 1.0.109 → 1.0.111
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.
|
@@ -70,10 +70,10 @@ const loaderInterceptor = (req, next) => {
|
|
|
70
70
|
const skipLoaderContext = req.context.get(SKIP_LOADER);
|
|
71
71
|
const skipLoaderHeaders = req.headers.has(X_SKIP_LOADER);
|
|
72
72
|
const skipLoader = skipLoaderContext || skipLoaderHeaders;
|
|
73
|
-
|
|
74
|
-
if (skipLoader) return next(
|
|
73
|
+
const modifiedReq = skipLoaderHeaders ? req.clone({ headers: req.headers.delete(X_SKIP_LOADER) }) : req;
|
|
74
|
+
if (skipLoader) return next(modifiedReq);
|
|
75
75
|
loaderService.show();
|
|
76
|
-
return next(
|
|
76
|
+
return next(modifiedReq).pipe(finalize(() => loaderService.hide()));
|
|
77
77
|
};
|
|
78
78
|
const timeoutInterceptor = (req, next) => {
|
|
79
79
|
const defaultTimeout = req.context.get(REQUEST_TIMEOUT);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"kuzntsv-uikit-interceptors.mjs","names":[],"sources":["../../interceptors/src/api-prefix.interceptor.ts","../../interceptors/src/auth.interceptor.ts","../../interceptors/src/error.interceptor.ts","../../interceptors/src/loader.interceptor.ts","../../interceptors/src/timeout.interceptor.ts","../../interceptors/src/providers.ts","../../interceptors/src/public-api.ts"],"sourcesContent":["import { HttpInterceptorFn } from '@angular/common/http';\r\nimport { inject } from '@angular/core';\r\nimport { API_NAME, UIKIT_CONFIG } from '@kuzntsv/uikit';\r\n\r\nexport const apiPrefixInterceptor: HttpInterceptorFn = (req, next) => {\r\n // Если запрос уже абсолютный (начинается с http/https), игнорируем его\r\n if (req.url.startsWith('http://') || req.url.startsWith('https://')) {\r\n return next(req);\r\n }\r\n\r\n const config = inject(UIKIT_CONFIG);\r\n\r\n // Читаем, какой API запросил сервис (например, 'analytics')\r\n const apiKeys = req.context.get(API_NAME);\r\n\r\n // Ищем адрес в конфиге. Если не нашли такой ключ, берем 'default'\r\n const targetBaseUrl = config.apiUrls[apiKeys] || config.apiUrls['default'];\r\n\r\n if (!targetBaseUrl) {\r\n // Если даже default не настроен, пропускаем как есть\r\n return next(req);\r\n }\r\n\r\n // Нормализуем слеши при склеивании\r\n const baseUrl = targetBaseUrl.replace(/\\/$/, '');\r\n const url = req.url.startsWith('/') ? req.url : `/${req.url}`;\r\n\r\n const clonedReq = req.clone({ url: `${baseUrl}${url}` });\r\n return next(clonedReq);\r\n};\r\n","import { HttpErrorResponse, HttpInterceptorFn } from '@angular/common/http';\r\nimport { inject } from '@angular/core';\r\nimport { BYPASS_AUTH, UIKIT_CONFIG } from '@kuzntsv/uikit';\r\nimport { throwError } from 'rxjs';\r\n\r\nexport const coreAuthInterceptor: HttpInterceptorFn = (req, next) => {\r\n const config = inject(UIKIT_CONFIG);\r\n\r\n const isAuthDisabled = !!config.auth.disabled;\r\n if (isAuthDisabled) {\r\n return next(req); // авторизация полностью отключена\r\n }\r\n\r\n // 0. маркер пропуска авторизации\r\n if (req.context.get(BYPASS_AUTH) === true) {\r\n return next(req); // Пропускаем проверку токенов для этого запроса\r\n }\r\n\r\n // 1. Проверяем, является ли URL публичным (не требующим авторизации)\r\n const isPublic = config.auth.publicUrls.some((url) => req.url.includes(url));\r\n if (isPublic) {\r\n return next(req);\r\n }\r\n\r\n // 2. Если в заголовках уже есть Authorization (например, передан вручную), отправляем как есть\r\n if (req.headers.has('Authorization')) {\r\n return next(req);\r\n }\r\n\r\n // 3. Пытаемся достать токен из LocalStorage по ключу из конфигурации\r\n const storageValue = localStorage.getItem(config.auth.userStorageKey);\r\n const user = storageValue ? JSON.parse(storageValue) : null;\r\n\r\n if (user && user.token) {\r\n // 4. Если токен есть, клонируем запрос и добавляем заголовок Bearer\r\n const authReq = req.clone({\r\n headers: req.headers\r\n .set('Authorization', `Bearer ${user.token}`)\r\n .set('User-Id', String(user.id))\r\n .set('Human-Id', String(user.idHuman)),\r\n });\r\n return next(authReq);\r\n }\r\n\r\n // 5. Если токена нет и запрос приватный — прерываем и редиректим на страницу логина\r\n // router.navigate([config.auth.loginRoute]);\r\n // 5. Если токена нет и запрос приватный — не редиректим вручную!\r\n // Просто кидаем 401 ошибку. Она сама прилетит в coreErrorInterceptor.\r\n return throwError(\r\n () =>\r\n new HttpErrorResponse({\r\n status: 401,\r\n statusText: 'Unauthorized',\r\n url: req.url,\r\n error: { message:'Запрос отменен: пользователь не авторизован' },\r\n }),\r\n );\r\n};\r\n","import { HttpInterceptorFn } from '@angular/common/http';\r\nimport { inject } from '@angular/core';\r\nimport { catchError, throwError, TimeoutError } from 'rxjs';\r\nimport {\r\n UIKIT_NOTIFICATION_TOKEN,\r\n AuthEventService,\r\n SKIP_NOTIFICATION,\r\n X_SKIP_NOTIFICATION,\r\n} from '@kuzntsv/uikit';\r\n\r\n// Состояние для управления очередью запросов во время рефреша\r\nlet isRefreshing = false; // флаг того, что обновление уже идет\r\n// для ожидания токена\r\n// const refreshTokenSubject: BehaviorSubject<string | null> = new BehaviorSubject<string | null>(null);\r\n\r\nexport const coreErrorInterceptor: HttpInterceptorFn = (req, next) => {\r\n const notification = inject(UIKIT_NOTIFICATION_TOKEN, { optional: true });\r\n const eventService = inject(AuthEventService);\r\n\r\n // Читаем значение токена из контекста запроса\r\n const skipNotificationContext = req.context.get(SKIP_NOTIFICATION);\r\n // Проверяем текстовый заголовок\r\n const skipNotificationHeaders = req.headers.has(X_SKIP_NOTIFICATION);\r\n const skipNotification = skipNotificationContext || skipNotificationHeaders;\r\n\r\n // Очищаем запрос от технического заголовка перед отправкой в сеть\r\n const modifiedReq = skipNotificationHeaders \r\n ? req.clone({ headers: req.headers.delete(X_SKIP_NOTIFICATION) })\r\n : req;\r\n\r\n return next(modifiedReq).pipe(\r\n catchError((error: any) => {\r\n // Меняем тип на any, так как RxJS TimeoutError — это не HttpErrorResponse\r\n let errorMsg = 'Произошла непредвиденная ошибка';\r\n // Проверяем, что это ошибка 401 и запрос не шел на сам эндпоинт рефреша\r\n // const isRefreshRequest = req.url.includes(config.auth.refreshEndpoint);\r\n\r\n // 1. Проверяем, не является ли ошибка таймаутом RxJS\r\n if (error instanceof TimeoutError) {\r\n errorMsg =\r\n 'Время ожидания ответа от сервера истекло. Попробуйте позже.';\r\n // Если стоит флаг скрытия, не показываем ошибку таймаута\r\n if (!skipNotification) notification?.error(errorMsg);\r\n return throwError(() => new Error(errorMsg));\r\n }\r\n\r\n switch (error.status) {\r\n case 0:\r\n errorMsg = 'Сервер не доступен';\r\n break;\r\n case 401:\r\n // if (!isRefreshRequest) {\r\n // // return handle401Error(req, next, authService, config);\r\n // }\r\n // else {\r\n eventService.notify401(); // Просто кидаем событие в шину\r\n errorMsg = 'Сессия истекла. Войдите заново.';\r\n // }\r\n break;\r\n case 403:\r\n errorMsg = 'Недостаточно прав для выполнения действия.';\r\n break;\r\n case 404:\r\n errorMsg = 'Ресурс не найден';\r\n\r\n break;\r\n case 500:\r\n errorMsg = 'Ошибка сервера. Попробуйте позже.';\r\n break;\r\n default:\r\n // Для 400 и прочих берем сообщение с бэка или дефолтное\r\n errorMsg = error.error?.message || error.message || errorMsg;\r\n break;\r\n }\r\n\r\n if (!skipNotification) notification?.error(errorMsg);\r\n\r\n // Возвращаем исходную ошибку, чтобы вызывающий сервис/компонент\r\n // мог прочитать error.status или error.error при необходимости,\r\n // но при этом подменяем или дополняем сообщение.\r\n return throwError(() => error);\r\n }),\r\n );\r\n};\r\n\r\n// // Функция управления очередью и вызова Refresh Token\r\n// function handle401Error(\r\n// req: HttpRequest<unknown>,\r\n// next: HttpHandlerFn,\r\n// authService: LibraryAuthService,\r\n// config: UikitConfig\r\n// ): Observable<any> {\r\n\r\n// if (!isRefreshing) {\r\n// isRefreshing = true;\r\n// refreshTokenSubject.next(null); // Сбрасываем старый токен в потоке ожидания\r\n\r\n// return authService.refreshToken().pipe(\r\n// switchMap((response) => {\r\n// isRefreshing = false;\r\n// refreshTokenSubject.next(response.accessToken); // Разблокируем стоящие в очереди запросы\r\n\r\n// // Повторяем текущий упавший запрос с НОВЫМ токеном\r\n// return next(injectNewToken(req, response.accessToken, config));\r\n// }),\r\n// catchError((refreshError) => {\r\n// isRefreshing = false;\r\n// return throwError(() => refreshError);\r\n// })\r\n// );\r\n// } else {\r\n// // Если обновление уже запущено другим запросом, текущий запрос \"встает в очередь\"\r\n// // и ждет, пока в refreshTokenSubject появится новое строковое значение токена\r\n// return refreshTokenSubject.pipe(\r\n// filter(token => token !== null),\r\n// take(1), // Берем только первое валидное значение и отписываемся\r\n// switchMap(newToken => {\r\n// // Повторяем запрос с обновленным токеном\r\n// return next(injectNewToken(req, newToken!, config));\r\n// })\r\n// );\r\n// }\r\n// }\r\n\r\n// // Хелпер для подстановки свежего токена в заголовки повторяемого запроса\r\n// function injectNewToken(req: HttpRequest<unknown>, token: string, config: any): HttpRequest<unknown> {\r\n// return req.clone({\r\n// headers: req.headers.set('Authorization', `Bearer ${token}`)\r\n// });\r\n// }\r\n","import { HttpInterceptorFn } from '@angular/common/http';\r\nimport { inject } from '@angular/core';\r\nimport { LoaderService, SKIP_LOADER, X_SKIP_LOADER, X_SKIP_NOTIFICATION } from '@kuzntsv/uikit';\r\nimport { finalize } from 'rxjs';\r\n\r\nexport const loaderInterceptor: HttpInterceptorFn = (req, next) => {\r\n const loaderService = inject(LoaderService);\r\n\r\n // Читаем значение токена из контекста запроса\r\n const skipLoaderContext = req.context.get(SKIP_LOADER);\r\n // Проверяем текстовый заголовок\r\n const skipLoaderHeaders = req.headers.has(X_SKIP_LOADER);\r\n const skipLoader = skipLoaderContext || skipLoaderHeaders;\r\n console.log('skipLoaderHeaders=', skipLoaderHeaders);\r\n\r\n // // Очищаем запрос от технического заголовка перед отправкой в сеть\r\n // const modifiedReq = skipLoaderHeaders \r\n // ? req.clone({ headers: req.headers.delete(X_SKIP_LOADER) })\r\n // : req;\r\n\r\n // Если токен передан со значением true, просто пропускаем запрос дальше\r\n if (skipLoader) {\r\n return next(req);\r\n }\r\n\r\n loaderService.show();\r\n return next(req).pipe(finalize(() => loaderService.hide()));\r\n};\r\n","import { HttpInterceptorFn } from '@angular/common/http';\r\nimport { timeout } from 'rxjs/operators';\r\nimport { REQUEST_TIMEOUT } from '@kuzntsv/uikit';\r\n\r\nexport const timeoutInterceptor: HttpInterceptorFn = (req, next) => {\r\n const defaultTimeout = req.context.get(REQUEST_TIMEOUT);\r\n\r\n return next(req).pipe(\r\n timeout(defaultTimeout)\r\n );\r\n};","import { InjectionToken, makeEnvironmentProviders, EnvironmentProviders } from '@angular/core';\r\nimport { HttpInterceptorFn } from '@angular/common/http';\r\nimport { UIKIT_CONFIG, UikitConfig } from '@kuzntsv/uikit';\r\n\r\nimport { UikitInterceptors } from './public-api';\r\n\r\n// Это кастомный Multi-InjectionToken, в который мы прячем интерцепторы библиотеки\r\nexport const UIKIT_INTERCEPTORS = new InjectionToken<HttpInterceptorFn[]>('UikitInterceptors');\r\n\r\nexport function provideUikit(config: UikitConfig): EnvironmentProviders {\r\n return makeEnvironmentProviders([\r\n { provide: UIKIT_CONFIG, useValue: config },\r\n \r\n // Регистрируем интерцептор внутри нашего собственного токена\r\n { provide: UIKIT_INTERCEPTORS, useValue: UikitInterceptors, multi: true }\r\n ]);\r\n};\r\n\r\n","import { apiPrefixInterceptor } from './api-prefix.interceptor';\r\nimport { coreAuthInterceptor } from './auth.interceptor';\r\nimport { coreErrorInterceptor } from './error.interceptor';\r\nimport { loaderInterceptor } from './loader.interceptor';\r\nimport { timeoutInterceptor } from './timeout.interceptor';\r\n\r\n// Экспортируем готовый пирог для приложений\r\n// Порядок интерцепторов очень важен. Сначала должен отработать интерцептор авторизации\r\n// (чтобы проверить оригинальный относительный URL и прикрепить токен),\r\n// а затем интерцептор подстановки Base URL (превращающий путь в абсолютный).\r\nexport const UikitInterceptors = [\r\n coreErrorInterceptor, // 1. Ловит абсолютно ВСЕ ошибки: и сетевые, и таймауты, и искусственные 401\r\n loaderInterceptor, // 2. Вторым — включаем лоадер (он закроется в finalize ПОСЛЕ обработки ошибок)\r\n apiPrefixInterceptor, // 3. Сначала превращаем URL в абсолютный (https://...)\r\n coreAuthInterceptor, // 4. Теперь проверяем publicUrls по полному пути и подставляем токен\r\n timeoutInterceptor, // 5. Ближе всего к сети — контролирует время выполнения самого запроса\r\n];\r\n\r\nexport * from './providers';\r\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"kuzntsv-uikit-interceptors.mjs","names":[],"sources":["../../interceptors/src/api-prefix.interceptor.ts","../../interceptors/src/auth.interceptor.ts","../../interceptors/src/error.interceptor.ts","../../interceptors/src/loader.interceptor.ts","../../interceptors/src/timeout.interceptor.ts","../../interceptors/src/providers.ts","../../interceptors/src/public-api.ts"],"sourcesContent":["import { HttpInterceptorFn } from '@angular/common/http';\r\nimport { inject } from '@angular/core';\r\nimport { API_NAME, UIKIT_CONFIG } from '@kuzntsv/uikit';\r\n\r\nexport const apiPrefixInterceptor: HttpInterceptorFn = (req, next) => {\r\n // Если запрос уже абсолютный (начинается с http/https), игнорируем его\r\n if (req.url.startsWith('http://') || req.url.startsWith('https://')) {\r\n return next(req);\r\n }\r\n\r\n const config = inject(UIKIT_CONFIG);\r\n\r\n // Читаем, какой API запросил сервис (например, 'analytics')\r\n const apiKeys = req.context.get(API_NAME);\r\n\r\n // Ищем адрес в конфиге. Если не нашли такой ключ, берем 'default'\r\n const targetBaseUrl = config.apiUrls[apiKeys] || config.apiUrls['default'];\r\n\r\n if (!targetBaseUrl) {\r\n // Если даже default не настроен, пропускаем как есть\r\n return next(req);\r\n }\r\n\r\n // Нормализуем слеши при склеивании\r\n const baseUrl = targetBaseUrl.replace(/\\/$/, '');\r\n const url = req.url.startsWith('/') ? req.url : `/${req.url}`;\r\n\r\n const clonedReq = req.clone({ url: `${baseUrl}${url}` });\r\n return next(clonedReq);\r\n};\r\n","import { HttpErrorResponse, HttpInterceptorFn } from '@angular/common/http';\r\nimport { inject } from '@angular/core';\r\nimport { BYPASS_AUTH, UIKIT_CONFIG } from '@kuzntsv/uikit';\r\nimport { throwError } from 'rxjs';\r\n\r\nexport const coreAuthInterceptor: HttpInterceptorFn = (req, next) => {\r\n const config = inject(UIKIT_CONFIG);\r\n\r\n const isAuthDisabled = !!config.auth.disabled;\r\n if (isAuthDisabled) {\r\n return next(req); // авторизация полностью отключена\r\n }\r\n\r\n // 0. маркер пропуска авторизации\r\n if (req.context.get(BYPASS_AUTH) === true) {\r\n return next(req); // Пропускаем проверку токенов для этого запроса\r\n }\r\n\r\n // 1. Проверяем, является ли URL публичным (не требующим авторизации)\r\n const isPublic = config.auth.publicUrls.some((url) => req.url.includes(url));\r\n if (isPublic) {\r\n return next(req);\r\n }\r\n\r\n // 2. Если в заголовках уже есть Authorization (например, передан вручную), отправляем как есть\r\n if (req.headers.has('Authorization')) {\r\n return next(req);\r\n }\r\n\r\n // 3. Пытаемся достать токен из LocalStorage по ключу из конфигурации\r\n const storageValue = localStorage.getItem(config.auth.userStorageKey);\r\n const user = storageValue ? JSON.parse(storageValue) : null;\r\n\r\n if (user && user.token) {\r\n // 4. Если токен есть, клонируем запрос и добавляем заголовок Bearer\r\n const authReq = req.clone({\r\n headers: req.headers\r\n .set('Authorization', `Bearer ${user.token}`)\r\n .set('User-Id', String(user.id))\r\n .set('Human-Id', String(user.idHuman)),\r\n });\r\n return next(authReq);\r\n }\r\n\r\n // 5. Если токена нет и запрос приватный — прерываем и редиректим на страницу логина\r\n // router.navigate([config.auth.loginRoute]);\r\n // 5. Если токена нет и запрос приватный — не редиректим вручную!\r\n // Просто кидаем 401 ошибку. Она сама прилетит в coreErrorInterceptor.\r\n return throwError(\r\n () =>\r\n new HttpErrorResponse({\r\n status: 401,\r\n statusText: 'Unauthorized',\r\n url: req.url,\r\n error: { message:'Запрос отменен: пользователь не авторизован' },\r\n }),\r\n );\r\n};\r\n","import { HttpInterceptorFn } from '@angular/common/http';\r\nimport { inject } from '@angular/core';\r\nimport { catchError, throwError, TimeoutError } from 'rxjs';\r\nimport {\r\n UIKIT_NOTIFICATION_TOKEN,\r\n AuthEventService,\r\n SKIP_NOTIFICATION,\r\n X_SKIP_NOTIFICATION,\r\n} from '@kuzntsv/uikit';\r\n\r\n// Состояние для управления очередью запросов во время рефреша\r\nlet isRefreshing = false; // флаг того, что обновление уже идет\r\n// для ожидания токена\r\n// const refreshTokenSubject: BehaviorSubject<string | null> = new BehaviorSubject<string | null>(null);\r\n\r\nexport const coreErrorInterceptor: HttpInterceptorFn = (req, next) => {\r\n const notification = inject(UIKIT_NOTIFICATION_TOKEN, { optional: true });\r\n const eventService = inject(AuthEventService);\r\n\r\n // Читаем значение токена из контекста запроса\r\n const skipNotificationContext = req.context.get(SKIP_NOTIFICATION);\r\n // Проверяем текстовый заголовок\r\n const skipNotificationHeaders = req.headers.has(X_SKIP_NOTIFICATION);\r\n const skipNotification = skipNotificationContext || skipNotificationHeaders;\r\n\r\n // Очищаем запрос от технического заголовка перед отправкой в сеть\r\n const modifiedReq = skipNotificationHeaders \r\n ? req.clone({ headers: req.headers.delete(X_SKIP_NOTIFICATION) })\r\n : req;\r\n\r\n return next(modifiedReq).pipe(\r\n catchError((error: any) => {\r\n // Меняем тип на any, так как RxJS TimeoutError — это не HttpErrorResponse\r\n let errorMsg = 'Произошла непредвиденная ошибка';\r\n // Проверяем, что это ошибка 401 и запрос не шел на сам эндпоинт рефреша\r\n // const isRefreshRequest = req.url.includes(config.auth.refreshEndpoint);\r\n\r\n // 1. Проверяем, не является ли ошибка таймаутом RxJS\r\n if (error instanceof TimeoutError) {\r\n errorMsg =\r\n 'Время ожидания ответа от сервера истекло. Попробуйте позже.';\r\n // Если стоит флаг скрытия, не показываем ошибку таймаута\r\n if (!skipNotification) notification?.error(errorMsg);\r\n return throwError(() => new Error(errorMsg));\r\n }\r\n\r\n switch (error.status) {\r\n case 0:\r\n errorMsg = 'Сервер не доступен';\r\n break;\r\n case 401:\r\n // if (!isRefreshRequest) {\r\n // // return handle401Error(req, next, authService, config);\r\n // }\r\n // else {\r\n eventService.notify401(); // Просто кидаем событие в шину\r\n errorMsg = 'Сессия истекла. Войдите заново.';\r\n // }\r\n break;\r\n case 403:\r\n errorMsg = 'Недостаточно прав для выполнения действия.';\r\n break;\r\n case 404:\r\n errorMsg = 'Ресурс не найден';\r\n\r\n break;\r\n case 500:\r\n errorMsg = 'Ошибка сервера. Попробуйте позже.';\r\n break;\r\n default:\r\n // Для 400 и прочих берем сообщение с бэка или дефолтное\r\n errorMsg = error.error?.message || error.message || errorMsg;\r\n break;\r\n }\r\n\r\n if (!skipNotification) notification?.error(errorMsg);\r\n\r\n // Возвращаем исходную ошибку, чтобы вызывающий сервис/компонент\r\n // мог прочитать error.status или error.error при необходимости,\r\n // но при этом подменяем или дополняем сообщение.\r\n return throwError(() => error);\r\n }),\r\n );\r\n};\r\n\r\n// // Функция управления очередью и вызова Refresh Token\r\n// function handle401Error(\r\n// req: HttpRequest<unknown>,\r\n// next: HttpHandlerFn,\r\n// authService: LibraryAuthService,\r\n// config: UikitConfig\r\n// ): Observable<any> {\r\n\r\n// if (!isRefreshing) {\r\n// isRefreshing = true;\r\n// refreshTokenSubject.next(null); // Сбрасываем старый токен в потоке ожидания\r\n\r\n// return authService.refreshToken().pipe(\r\n// switchMap((response) => {\r\n// isRefreshing = false;\r\n// refreshTokenSubject.next(response.accessToken); // Разблокируем стоящие в очереди запросы\r\n\r\n// // Повторяем текущий упавший запрос с НОВЫМ токеном\r\n// return next(injectNewToken(req, response.accessToken, config));\r\n// }),\r\n// catchError((refreshError) => {\r\n// isRefreshing = false;\r\n// return throwError(() => refreshError);\r\n// })\r\n// );\r\n// } else {\r\n// // Если обновление уже запущено другим запросом, текущий запрос \"встает в очередь\"\r\n// // и ждет, пока в refreshTokenSubject появится новое строковое значение токена\r\n// return refreshTokenSubject.pipe(\r\n// filter(token => token !== null),\r\n// take(1), // Берем только первое валидное значение и отписываемся\r\n// switchMap(newToken => {\r\n// // Повторяем запрос с обновленным токеном\r\n// return next(injectNewToken(req, newToken!, config));\r\n// })\r\n// );\r\n// }\r\n// }\r\n\r\n// // Хелпер для подстановки свежего токена в заголовки повторяемого запроса\r\n// function injectNewToken(req: HttpRequest<unknown>, token: string, config: any): HttpRequest<unknown> {\r\n// return req.clone({\r\n// headers: req.headers.set('Authorization', `Bearer ${token}`)\r\n// });\r\n// }\r\n","import { HttpInterceptorFn } from '@angular/common/http';\r\nimport { inject } from '@angular/core';\r\nimport { LoaderService, SKIP_LOADER, X_SKIP_LOADER, X_SKIP_NOTIFICATION } from '@kuzntsv/uikit';\r\nimport { finalize } from 'rxjs';\r\n\r\nexport const loaderInterceptor: HttpInterceptorFn = (req, next) => {\r\n const loaderService = inject(LoaderService);\r\n\r\n // Читаем значение токена из контекста запроса\r\n const skipLoaderContext = req.context.get(SKIP_LOADER);\r\n // Проверяем текстовый заголовок\r\n const skipLoaderHeaders = req.headers.has(X_SKIP_LOADER);\r\n const skipLoader = skipLoaderContext || skipLoaderHeaders;\r\n\r\n // Очищаем запрос от технического заголовка перед отправкой в сеть\r\n const modifiedReq = skipLoaderHeaders \r\n ? req.clone({ headers: req.headers.delete(X_SKIP_LOADER) })\r\n : req;\r\n\r\n // Если токен передан со значением true, просто пропускаем запрос дальше\r\n if (skipLoader) {\r\n return next(modifiedReq);\r\n }\r\n\r\n loaderService.show();\r\n return next(modifiedReq).pipe(finalize(() => loaderService.hide()));\r\n};\r\n","import { HttpInterceptorFn } from '@angular/common/http';\r\nimport { timeout } from 'rxjs/operators';\r\nimport { REQUEST_TIMEOUT } from '@kuzntsv/uikit';\r\n\r\nexport const timeoutInterceptor: HttpInterceptorFn = (req, next) => {\r\n const defaultTimeout = req.context.get(REQUEST_TIMEOUT);\r\n\r\n return next(req).pipe(\r\n timeout(defaultTimeout)\r\n );\r\n};","import { InjectionToken, makeEnvironmentProviders, EnvironmentProviders } from '@angular/core';\r\nimport { HttpInterceptorFn } from '@angular/common/http';\r\nimport { UIKIT_CONFIG, UikitConfig } from '@kuzntsv/uikit';\r\n\r\nimport { UikitInterceptors } from './public-api';\r\n\r\n// Это кастомный Multi-InjectionToken, в который мы прячем интерцепторы библиотеки\r\nexport const UIKIT_INTERCEPTORS = new InjectionToken<HttpInterceptorFn[]>('UikitInterceptors');\r\n\r\nexport function provideUikit(config: UikitConfig): EnvironmentProviders {\r\n return makeEnvironmentProviders([\r\n { provide: UIKIT_CONFIG, useValue: config },\r\n \r\n // Регистрируем интерцептор внутри нашего собственного токена\r\n { provide: UIKIT_INTERCEPTORS, useValue: UikitInterceptors, multi: true }\r\n ]);\r\n};\r\n\r\n","import { apiPrefixInterceptor } from './api-prefix.interceptor';\r\nimport { coreAuthInterceptor } from './auth.interceptor';\r\nimport { coreErrorInterceptor } from './error.interceptor';\r\nimport { loaderInterceptor } from './loader.interceptor';\r\nimport { timeoutInterceptor } from './timeout.interceptor';\r\n\r\n// Экспортируем готовый пирог для приложений\r\n// Порядок интерцепторов очень важен. Сначала должен отработать интерцептор авторизации\r\n// (чтобы проверить оригинальный относительный URL и прикрепить токен),\r\n// а затем интерцептор подстановки Base URL (превращающий путь в абсолютный).\r\nexport const UikitInterceptors = [\r\n coreErrorInterceptor, // 1. Ловит абсолютно ВСЕ ошибки: и сетевые, и таймауты, и искусственные 401\r\n loaderInterceptor, // 2. Вторым — включаем лоадер (он закроется в finalize ПОСЛЕ обработки ошибок)\r\n apiPrefixInterceptor, // 3. Сначала превращаем URL в абсолютный (https://...)\r\n coreAuthInterceptor, // 4. Теперь проверяем publicUrls по полному пути и подставляем токен\r\n timeoutInterceptor, // 5. Ближе всего к сети — контролирует время выполнения самого запроса\r\n];\r\n\r\nexport * from './providers';\r\n"],"mappings":"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"}
|
|
@@ -2234,7 +2234,7 @@ var BaseSignalPageClass = class BaseSignalPageClass {
|
|
|
2234
2234
|
executeRoles = [];
|
|
2235
2235
|
hideRoles = [HIDE_ROLE_MARKER];
|
|
2236
2236
|
allowMultiSelect = false;
|
|
2237
|
-
selected = [];
|
|
2237
|
+
selected = signal([], ...ngDevMode ? [{ debugName: "selected" }] : /* istanbul ignore next */ []);
|
|
2238
2238
|
fnMapEntity;
|
|
2239
2239
|
dataResource;
|
|
2240
2240
|
countResource;
|
|
@@ -2408,9 +2408,9 @@ var BaseSignalPageClass = class BaseSignalPageClass {
|
|
|
2408
2408
|
}));
|
|
2409
2409
|
}
|
|
2410
2410
|
onSelectionChange(selected) {
|
|
2411
|
-
this.selected
|
|
2412
|
-
this.editBtnDisabledUpdate(
|
|
2413
|
-
this.removeBtnDisabledUpdate(
|
|
2411
|
+
this.selected.set(selected);
|
|
2412
|
+
this.editBtnDisabledUpdate(selected);
|
|
2413
|
+
this.removeBtnDisabledUpdate(selected);
|
|
2414
2414
|
}
|
|
2415
2415
|
editBtnDisabledUpdate(selected) {
|
|
2416
2416
|
this.editBtnDisabled = !(selected && selected.length === 1);
|
|
@@ -2462,11 +2462,11 @@ var BaseSignalPageClass = class BaseSignalPageClass {
|
|
|
2462
2462
|
} else this.notificationService.warning("Действие не доступно.");
|
|
2463
2463
|
}
|
|
2464
2464
|
editEvent() {
|
|
2465
|
-
if (this.selected && this.selected.length === 1) {
|
|
2465
|
+
if (this.selected() && this.selected().length === 1) {
|
|
2466
2466
|
if (this.entityEditComponent) {
|
|
2467
2467
|
const data = {
|
|
2468
2468
|
action: Action.Edit,
|
|
2469
|
-
entity: this.formModelToDomainModel(this.selected[0])
|
|
2469
|
+
entity: this.formModelToDomainModel(this.selected()[0])
|
|
2470
2470
|
};
|
|
2471
2471
|
if (this.beforeOpenDialogOnEditEvent) this.beforeOpenDialogOnEditEvent(data.entity, data.extraData);
|
|
2472
2472
|
const dialogRef = this.openDialog(this.entityEditComponent, data, this.dialog);
|
|
@@ -2503,13 +2503,13 @@ var BaseSignalPageClass = class BaseSignalPageClass {
|
|
|
2503
2503
|
}
|
|
2504
2504
|
}
|
|
2505
2505
|
removeEvent() {
|
|
2506
|
-
if (this.selected && this.selected.length > 0) {
|
|
2506
|
+
if (this.selected() && this.selected().length > 0) {
|
|
2507
2507
|
const dialogRef = this.openDialogRemoveConfirm(this.removalMessage);
|
|
2508
2508
|
if (dialogRef) {
|
|
2509
2509
|
const subs = dialogRef.afterClosed().subscribe((result) => {
|
|
2510
2510
|
if (result === "delete") {
|
|
2511
2511
|
const sourcesObject = {};
|
|
2512
|
-
this.selected.forEach((item, index) => sourcesObject["obj" + index] = this.entityService.deleteObject(item));
|
|
2512
|
+
this.selected().forEach((item, index) => sourcesObject["obj" + index] = this.entityService.deleteObject(item));
|
|
2513
2513
|
const subs = forkJoin(sourcesObject).subscribe({
|
|
2514
2514
|
next: () => {
|
|
2515
2515
|
this.refreshEvent();
|
|
@@ -2552,7 +2552,7 @@ var BaseSignalPageClass = class BaseSignalPageClass {
|
|
|
2552
2552
|
}
|
|
2553
2553
|
openDialogRemoveConfirm(removalMessage) {
|
|
2554
2554
|
let message = "Вы хотите удалить ";
|
|
2555
|
-
message += removalMessage ? removalMessage : this.allowMultiSelect ? `выбранные записи (${this.selected.length})?` : "выбранную запись?";
|
|
2555
|
+
message += removalMessage ? removalMessage : this.allowMultiSelect ? `выбранные записи (${this.selected().length})?` : "выбранную запись?";
|
|
2556
2556
|
return this.dialog.open(DialogComponent, {
|
|
2557
2557
|
width: this.dialogRemoveWidth,
|
|
2558
2558
|
data: {
|
|
@@ -4713,8 +4713,10 @@ var TableComponent = class TableComponent {
|
|
|
4713
4713
|
this.dataSource.data = [...this.data()];
|
|
4714
4714
|
const currentData = this.data();
|
|
4715
4715
|
const model = this.selection();
|
|
4716
|
-
|
|
4717
|
-
|
|
4716
|
+
untracked(() => {
|
|
4717
|
+
const nextSelected = currentData.filter((item) => model.isSelected(item));
|
|
4718
|
+
if (nextSelected.length > 0) model.select(...nextSelected);
|
|
4719
|
+
});
|
|
4718
4720
|
});
|
|
4719
4721
|
effect(() => {
|
|
4720
4722
|
const selectedArray = this.selection().selected();
|