@kuzntsv/uikit 1.0.90 → 1.0.93
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/fesm2022/kuzntsv-uikit-interceptors.mjs +4 -3
- package/fesm2022/kuzntsv-uikit-interceptors.mjs.map +1 -1
- package/fesm2022/kuzntsv-uikit-ui.mjs +24 -17
- package/fesm2022/kuzntsv-uikit-ui.mjs.map +1 -1
- package/fesm2022/kuzntsv-uikit.mjs +2 -1
- package/fesm2022/kuzntsv-uikit.mjs.map +1 -1
- package/package.json +1 -1
- package/types/kuzntsv-uikit-ui.d.ts +33 -34
- package/types/kuzntsv-uikit.d.ts +1 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { InjectionToken, inject, makeEnvironmentProviders } from "@angular/core";
|
|
2
|
-
import { API_NAME, AuthEventService, BYPASS_AUTH, LoaderService, REQUEST_TIMEOUT, SKIP_LOADER, UIKIT_CONFIG, UIKIT_NOTIFICATION_TOKEN } from "@kuzntsv/uikit";
|
|
2
|
+
import { API_NAME, AuthEventService, BYPASS_AUTH, LoaderService, REQUEST_TIMEOUT, SKIP_LOADER, SKIP_NOTIFICATION, UIKIT_CONFIG, UIKIT_NOTIFICATION_TOKEN } from "@kuzntsv/uikit";
|
|
3
3
|
import { HttpErrorResponse } from "@angular/common/http";
|
|
4
4
|
import { TimeoutError, catchError, finalize, throwError } from "rxjs";
|
|
5
5
|
import { timeout } from "rxjs/operators";
|
|
@@ -32,11 +32,12 @@ const coreAuthInterceptor = (req, next) => {
|
|
|
32
32
|
const coreErrorInterceptor = (req, next) => {
|
|
33
33
|
const notification = inject(UIKIT_NOTIFICATION_TOKEN, { optional: true });
|
|
34
34
|
const eventService = inject(AuthEventService);
|
|
35
|
+
const skipNotification = req.context.get(SKIP_NOTIFICATION);
|
|
35
36
|
return next(req).pipe(catchError((error) => {
|
|
36
37
|
let errorMsg = "Произошла непредвиденная ошибка";
|
|
37
38
|
if (error instanceof TimeoutError) {
|
|
38
39
|
errorMsg = "Время ожидания ответа от сервера истекло. Попробуйте позже.";
|
|
39
|
-
notification?.error(errorMsg);
|
|
40
|
+
if (!skipNotification) notification?.error(errorMsg);
|
|
40
41
|
return throwError(() => new Error(errorMsg));
|
|
41
42
|
}
|
|
42
43
|
switch (error.status) {
|
|
@@ -58,7 +59,7 @@ const coreErrorInterceptor = (req, next) => {
|
|
|
58
59
|
break;
|
|
59
60
|
default: errorMsg = error.error?.message || error.message || errorMsg;
|
|
60
61
|
}
|
|
61
|
-
notification?.error(errorMsg);
|
|
62
|
+
if (!skipNotification) notification?.error(errorMsg);
|
|
62
63
|
return throwError(() => error);
|
|
63
64
|
}));
|
|
64
65
|
};
|
|
@@ -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 { UIKIT_NOTIFICATION_TOKEN, AuthEventService } from '@kuzntsv/uikit';\r\n\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 authService = inject(LibraryAuthService);\r\n // const config = inject(UIKIT_CONFIG);\r\n\r\n const eventService = inject(AuthEventService);\r\n\r\n return next(req).pipe(\r\n catchError((error: any) => { // Меняем тип на 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 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 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\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// }","import { HttpInterceptorFn } from '@angular/common/http';\r\nimport { inject } from '@angular/core';\r\nimport { LoaderService, SKIP_LOADER } 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 // Если токен передан со значением true, просто пропускаем запрос дальше\r\n if (req.context.get(SKIP_LOADER)) {\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":";;;;;AAIA,MAAa,wBAA2C,KAAK,SAAQ;CAEnE,IAAI,IAAI,IAAI,WAAW,SAAS,KAAK,IAAI,IAAI,WAAW,UAAU,GAChE,OAAO,KAAK,GAAG;CAGjB,MAAM,SAAS,OAAO,YAAY;CAGlC,MAAM,UAAU,IAAI,QAAQ,IAAI,QAAQ;CAGxC,MAAM,gBAAgB,OAAO,QAAQ,YAAY,OAAO,QAAQ;CAEhE,IAAI,CAAC,eAEH,OAAO,KAAK,GAAG;CAIjB,MAAM,UAAU,cAAc,QAAQ,OAAO,EAAE;CAC/C,MAAM,MAAM,IAAI,IAAI,WAAW,GAAG,IAAI,IAAI,MAAM,IAAI,IAAI;CAGxD,OAAO,KADW,IAAI,MAAM,EAAE,KAAK,GAAG,UAAU,MAAK,CACzC,CAAS;AACvB;ACxBA,MAAa,uBAA0C,KAAK,SAAQ;CAClE,MAAM,SAAS,OAAO,YAAY;CAGlC,IAAI,CADoB,CAAC,OAAO,KAAK,UAEnC,OAAO,KAAK,GAAG;CAIjB,IAAI,IAAI,QAAQ,IAAI,WAAW,MAAM,MACnC,OAAO,KAAK,GAAG;CAKjB,IADiB,OAAO,KAAK,WAAW,MAAM,QAAQ,IAAI,IAAI,SAAS,GAAG,CACtE,GACF,OAAO,KAAK,GAAG;CAIjB,IAAI,IAAI,QAAQ,IAAI,eAAe,GACjC,OAAO,KAAK,GAAG;CAIjB,MAAM,eAAe,aAAa,QAAQ,OAAO,KAAK,cAAc;CACpE,MAAM,OAAO,eAAe,KAAK,MAAM,YAAY,IAAI;CAEvD,IAAI,QAAQ,KAAK,OAQf,OAAO,KANS,IAAI,MAAM,EACxB,SAAS,IAAI,QACV,IAAI,iBAAiB,UAAU,KAAK,OAAO,CAAC,CAC5C,IAAI,WAAW,OAAO,KAAK,EAAE,CAAC,CAAC,CAC/B,IAAI,YAAY,OAAO,KAAK,OAAO,CAAC,EACxC,CACW,CAAO;CAOrB,OAAO,iBAEH,IAAI,kBAAkB;EACpB,QAAQ;EACR,YAAY;EACZ,KAAK,IAAI;EACT,OAAO,EAAE,SAAQ,8CAA6C;CAC/D,CAAA,CAAC;AAER;AC9CA,MAAa,wBAA2C,KAAK,SAAQ;CACnE,MAAM,eAAe,OAAO,0BAA0B,EAAE,UAAU,KAAI,CAAE;CAIxE,MAAM,eAAe,OAAO,gBAAgB;CAE5C,OAAO,KAAK,GAAG,CAAC,CAAC,KACf,YAAY,UAAc;EACxB,IAAI,WAAW;EAKf,IAAI,iBAAiB,cAAc;GACjC,WAAW;GACX,cAAc,MAAM,QAAQ;GAC5B,OAAO,iBAAiB,IAAI,MAAM,QAAQ,CAAC;EAC7C;EAEA,QAAQ,MAAM,QAAd;GACE,KAAK;IACH,WAAW;IACX;GACF,KAAK;IAKD,aAAa,UAAS;IACtB,WAAW;IAEb;GACF,KAAK;IACH,WAAW;IACX;GACF,KAAK;IACH,WAAW;IAEX;GACF,KAAK;IACH,WAAW;IACX;GACF,SAEE,WAAW,MAAM,OAAO,WAAW,MAAM,WAAW;EAExD;EACA,cAAc,MAAM,QAAQ;EAK5B,OAAO,iBAAiB,KAAK;CAC/B,CAAC,CAAC;AAEN;AC9DA,MAAa,qBAAwC,KAAK,SAAQ;CAChE,MAAM,gBAAgB,OAAO,aAAa;CAG1C,IAAI,IAAI,QAAQ,IAAI,WAAW,GAC7B,OAAO,KAAK,GAAG;CAGjB,cAAc,KAAI;CAClB,OAAO,KAAK,GAAG,CAAC,CAAC,KAAK,eAAe,cAAc,KAAI,CAAE,CAAC;AAC5D;ACXA,MAAa,sBAAyC,KAAK,SAAQ;CACjE,MAAM,iBAAiB,IAAI,QAAQ,IAAI,eAAe;CAEtD,OAAO,KAAK,GAAG,CAAC,CAAC,KACf,QAAQ,cAAc,CAAC;AAE3B;ACHA,MAAa,qBAAqB,IAAI,eAAoC,mBAAmB;AAE7F,SAAgB,aAAa,QAAmB;CAC9C,OAAO,yBAAyB,CAC9B;EAAE,SAAS;EAAc,UAAU;CAAM,GAGzC;EAAE,SAAS;EAAoB,UAAU;EAAmB,OAAO;CAAI,CACxE,CAAA;AACH;ACNA,MAAa,oBAAoB;CAC/B;CACA;CACA;CACA;CACA;AACD"}
|
|
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 { UIKIT_NOTIFICATION_TOKEN, AuthEventService, SKIP_NOTIFICATION } from '@kuzntsv/uikit';\r\n\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 skipNotification = req.context.get(SKIP_NOTIFICATION);\r\n\r\n return next(req).pipe(\r\n catchError((error: any) => { // Меняем тип на 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 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\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// }","import { HttpInterceptorFn } from '@angular/common/http';\r\nimport { inject } from '@angular/core';\r\nimport { LoaderService, SKIP_LOADER } 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 // Если токен передан со значением true, просто пропускаем запрос дальше\r\n if (req.context.get(SKIP_LOADER)) {\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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"}
|
|
@@ -38,7 +38,7 @@ import { MatSort, MatSortModule } from "@angular/material/sort";
|
|
|
38
38
|
import * as i1 from "@angular/material/table";
|
|
39
39
|
import { MatTableDataSource, MatTableModule } from "@angular/material/table";
|
|
40
40
|
const loginSchema = z$1.object({
|
|
41
|
-
username: z$1.string().min(1, { message: "Пожалуйста, заполните это поле" }).regex(/^[A-Za-z]{1,}\.[A-Za-z]{1,}\.[A-Za-z]*$/, { message: "Пожалуйста, используйте
|
|
41
|
+
username: z$1.string().min(1, { message: "Пожалуйста, заполните это поле" }).regex(/^[A-Za-z]{1,}\.[A-Za-z]{1,}\.[A-Za-z]*$/, { message: "Пожалуйста, используйте формат \"a.p.petrov\"" }),
|
|
42
42
|
password: z$1.string().min(1, { message: "Пожалуйста, заполните это поле" })
|
|
43
43
|
});
|
|
44
44
|
var LoginFormComponent = class LoginFormComponent {
|
|
@@ -2265,10 +2265,9 @@ var BaseSignalPageClass = class BaseSignalPageClass {
|
|
|
2265
2265
|
}
|
|
2266
2266
|
initFilters() {
|
|
2267
2267
|
if (!this.filterSchema || !this.defaultFilters) return;
|
|
2268
|
-
const prefix = this.storagePrefix;
|
|
2269
2268
|
const initial = { ...this.defaultFilters };
|
|
2270
2269
|
Object.keys(this.defaultFilters).forEach((key) => {
|
|
2271
|
-
const storageKey = `${
|
|
2270
|
+
const storageKey = `${this.storagePrefix}${key}`;
|
|
2272
2271
|
const rawValue = this.localStorageService.getData(storageKey);
|
|
2273
2272
|
if (rawValue !== null && rawValue !== void 0) try {
|
|
2274
2273
|
initial[key] = JSON.parse(rawValue);
|
|
@@ -2279,15 +2278,6 @@ var BaseSignalPageClass = class BaseSignalPageClass {
|
|
|
2279
2278
|
const validation = this.filterSchema.safeParse(initial);
|
|
2280
2279
|
this.filters.set(validation.success ? validation.data : this.defaultFilters);
|
|
2281
2280
|
}
|
|
2282
|
-
clearEvent() {
|
|
2283
|
-
if (!this.defaultFilters) return;
|
|
2284
|
-
const prefix = this.storagePrefix;
|
|
2285
|
-
Object.keys(this.defaultFilters).forEach((key) => {
|
|
2286
|
-
localStorage.removeItem(`${prefix}${key}`);
|
|
2287
|
-
});
|
|
2288
|
-
this.filters.set({ ...this.defaultFilters });
|
|
2289
|
-
this.clearFilters.set({});
|
|
2290
|
-
}
|
|
2291
2281
|
getInitialFilters2() {
|
|
2292
2282
|
const prefix = this.constructor.name + "_Filter_";
|
|
2293
2283
|
const state = {};
|
|
@@ -2305,6 +2295,18 @@ var BaseSignalPageClass = class BaseSignalPageClass {
|
|
|
2305
2295
|
}
|
|
2306
2296
|
return state;
|
|
2307
2297
|
}
|
|
2298
|
+
clearEvent() {
|
|
2299
|
+
if (!this.defaultFilters) return;
|
|
2300
|
+
Object.keys(this.defaultFilters).forEach((key) => {
|
|
2301
|
+
this.localStorageService.removeData(`${this.storagePrefix}${key}`);
|
|
2302
|
+
});
|
|
2303
|
+
this.tableState.update((prev) => ({
|
|
2304
|
+
...prev,
|
|
2305
|
+
pageIndex: 0
|
|
2306
|
+
}));
|
|
2307
|
+
this.filters.set({ ...this.defaultFilters });
|
|
2308
|
+
this.clearFilters.set({});
|
|
2309
|
+
}
|
|
2308
2310
|
filters = signal({}, ...ngDevMode ? [{ debugName: "filters" }] : /* istanbul ignore next */ []);
|
|
2309
2311
|
clearFilters = signal(null, ...ngDevMode ? [{ debugName: "clearFilters" }] : /* istanbul ignore next */ []);
|
|
2310
2312
|
tableState = signal({
|
|
@@ -2323,15 +2325,14 @@ var BaseSignalPageClass = class BaseSignalPageClass {
|
|
|
2323
2325
|
let previousFilters = { ...this.filters() };
|
|
2324
2326
|
effect(() => {
|
|
2325
2327
|
const currentFilters = this.filters();
|
|
2326
|
-
const prefix = this.storagePrefix;
|
|
2327
2328
|
let filterChanged = false;
|
|
2328
2329
|
Object.keys(currentFilters).forEach((key) => {
|
|
2329
2330
|
const prevVal = previousFilters[key];
|
|
2330
2331
|
const currVal = currentFilters[key];
|
|
2331
2332
|
if (prevVal !== currVal) {
|
|
2332
2333
|
filterChanged = true;
|
|
2333
|
-
const storageKey = `${
|
|
2334
|
-
if (currVal === null || currVal === void 0 || currVal === "" || currVal === 0 || currVal === -1 || Array.isArray(currVal) && currVal.length === 0)
|
|
2334
|
+
const storageKey = `${this.storagePrefix}${String(key)}`;
|
|
2335
|
+
if (currVal === null || currVal === void 0 || currVal === "" || currVal === 0 || currVal === -1 || Array.isArray(currVal) && currVal.length === 0) this.localStorageService.removeData(storageKey);
|
|
2335
2336
|
else this.localStorageService.saveData(storageKey, JSON.stringify(currVal));
|
|
2336
2337
|
}
|
|
2337
2338
|
});
|
|
@@ -2418,7 +2419,7 @@ var BaseSignalPageClass = class BaseSignalPageClass {
|
|
|
2418
2419
|
}
|
|
2419
2420
|
newEvent() {
|
|
2420
2421
|
if (this.entityEditComponent) {
|
|
2421
|
-
const defaultEntity = this.
|
|
2422
|
+
const defaultEntity = this.schemaDTO.parse({});
|
|
2422
2423
|
const data = {
|
|
2423
2424
|
action: Action.Add,
|
|
2424
2425
|
entity: defaultEntity
|
|
@@ -2461,7 +2462,7 @@ var BaseSignalPageClass = class BaseSignalPageClass {
|
|
|
2461
2462
|
if (this.entityEditComponent) {
|
|
2462
2463
|
const data = {
|
|
2463
2464
|
action: Action.Edit,
|
|
2464
|
-
entity: this.selected[0]
|
|
2465
|
+
entity: this.formModelToDomainModel(this.selected[0])
|
|
2465
2466
|
};
|
|
2466
2467
|
if (this.beforeOpenDialogOnEditEvent) this.beforeOpenDialogOnEditEvent(data.entity, data.extraData);
|
|
2467
2468
|
const dialogRef = this.openDialog(this.entityEditComponent, data, this.dialog);
|
|
@@ -2576,6 +2577,12 @@ var BaseSignalPageClass = class BaseSignalPageClass {
|
|
|
2576
2577
|
}
|
|
2577
2578
|
};
|
|
2578
2579
|
}
|
|
2580
|
+
formModelToDomainModel(formModel, originalDomain) {
|
|
2581
|
+
return {
|
|
2582
|
+
...originalDomain,
|
|
2583
|
+
...formModel
|
|
2584
|
+
};
|
|
2585
|
+
}
|
|
2579
2586
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
2580
2587
|
minVersion: "12.0.0",
|
|
2581
2588
|
version: "22.2.2",
|