@kuzntsv/uikit 1.0.110 → 1.0.112

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,7 +70,6 @@ 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
- console.log("skipLoaderHeaders=", skipLoaderHeaders);
74
73
  const modifiedReq = skipLoaderHeaders ? req.clone({ headers: req.headers.delete(X_SKIP_LOADER) }) : req;
75
74
  if (skipLoader) return next(modifiedReq);
76
75
  loaderService.show();
@@ -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(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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"}
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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"}
@@ -2254,8 +2254,8 @@ var BaseSignalPageClass = class BaseSignalPageClass {
2254
2254
  if (this.dataResource.error() || this.countResource.error()) return false;
2255
2255
  return this.dataResource.isLoading() || this.countResource.isLoading();
2256
2256
  }, ...ngDevMode ? [{ debugName: "isLoading" }] : /* istanbul ignore next */ []);
2257
- editBtnDisabled = true;
2258
- removeBtnDisabled = true;
2257
+ editBtnDisabled = computed(() => !(this.selected() && this.selected().length === 1), ...ngDevMode ? [{ debugName: "editBtnDisabled" }] : /* istanbul ignore next */ []);
2258
+ removeBtnDisabled = computed(() => !(this.selected() && this.selected().length > 0), ...ngDevMode ? [{ debugName: "removeBtnDisabled" }] : /* istanbul ignore next */ []);
2259
2259
  dblClickedEventEnable = true;
2260
2260
  opt = [];
2261
2261
  router = inject(Router);
@@ -2409,14 +2409,6 @@ var BaseSignalPageClass = class BaseSignalPageClass {
2409
2409
  }
2410
2410
  onSelectionChange(selected) {
2411
2411
  this.selected.set(selected);
2412
- this.editBtnDisabledUpdate(selected);
2413
- this.removeBtnDisabledUpdate(selected);
2414
- }
2415
- editBtnDisabledUpdate(selected) {
2416
- this.editBtnDisabled = !(selected && selected.length === 1);
2417
- }
2418
- removeBtnDisabledUpdate(selected) {
2419
- this.removeBtnDisabled = !(selected && selected.length > 0);
2420
2412
  }
2421
2413
  onRowDoubleClicked() {
2422
2414
  this.editEvent();
@@ -2462,7 +2454,7 @@ var BaseSignalPageClass = class BaseSignalPageClass {
2462
2454
  } else this.notificationService.warning("Действие не доступно.");
2463
2455
  }
2464
2456
  editEvent() {
2465
- if (this.selected && this.selected.length === 1) {
2457
+ if (this.selected() && this.selected().length === 1) {
2466
2458
  if (this.entityEditComponent) {
2467
2459
  const data = {
2468
2460
  action: Action.Edit,
@@ -2503,7 +2495,7 @@ var BaseSignalPageClass = class BaseSignalPageClass {
2503
2495
  }
2504
2496
  }
2505
2497
  removeEvent() {
2506
- if (this.selected && this.selected.length > 0) {
2498
+ if (this.selected() && this.selected().length > 0) {
2507
2499
  const dialogRef = this.openDialogRemoveConfirm(this.removalMessage);
2508
2500
  if (dialogRef) {
2509
2501
  const subs = dialogRef.afterClosed().subscribe((result) => {
@@ -2552,7 +2544,7 @@ var BaseSignalPageClass = class BaseSignalPageClass {
2552
2544
  }
2553
2545
  openDialogRemoveConfirm(removalMessage) {
2554
2546
  let message = "Вы хотите удалить ";
2555
- message += removalMessage ? removalMessage : this.allowMultiSelect ? `выбранные записи (${this.selected.length})?` : "выбранную запись?";
2547
+ message += removalMessage ? removalMessage : this.allowMultiSelect ? `выбранные записи (${this.selected().length})?` : "выбранную запись?";
2556
2548
  return this.dialog.open(DialogComponent, {
2557
2549
  width: this.dialogRemoveWidth,
2558
2550
  data: {