@snabcentr/client-core 4.16.2 → 4.18.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.
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import * as i1 from 'rxjs';
|
|
2
|
-
import { of, startWith as startWith$1, Subject, ReplaySubject, partition, combineLatest, map as map$1, debounceTime, switchMap, tap, filter, shareReplay, distinctUntilChanged, BehaviorSubject, materialize, dematerialize, merge, distinctUntilKeyChanged, skip, first, catchError as catchError$1, throwError, expand, takeWhile, toArray, EMPTY, take, defaultIfEmpty, concatMap, finalize, takeUntil, timeout, interval, share, scan } from 'rxjs';
|
|
2
|
+
import { of, startWith as startWith$1, Subject, ReplaySubject, partition, combineLatest, map as map$1, debounceTime, switchMap, tap, filter, shareReplay, distinctUntilChanged, BehaviorSubject, materialize, dematerialize, merge, distinctUntilKeyChanged, skip, first, catchError as catchError$1, throwError, expand, takeWhile, toArray, EMPTY, take, defaultIfEmpty, concatMap, finalize, takeUntil, timeout, interval, share, scan, defer, from, lastValueFrom, TimeoutError } from 'rxjs';
|
|
3
3
|
import { map, startWith, catchError } from 'rxjs/operators';
|
|
4
4
|
import { parseISO, parse, isValid, format } from 'date-fns';
|
|
5
5
|
import { isString, constant, isNil, isObject, isDate, isMatch, isArray, omit, get, isNumber, isBoolean } from 'lodash-es';
|
|
6
6
|
import * as i0 from '@angular/core';
|
|
7
|
-
import { InjectionToken, inject, PLATFORM_ID, Inject, Optional, Injectable, EventEmitter, DestroyRef, LOCALE_ID, Pipe, RESPONSE_INIT, assertInInjectionContext, RendererFactory2 } from '@angular/core';
|
|
7
|
+
import { InjectionToken, inject, PLATFORM_ID, Inject, Optional, Injectable, EventEmitter, DestroyRef, LOCALE_ID, Pipe, RESPONSE_INIT, assertInInjectionContext, RendererFactory2, makeEnvironmentProviders } from '@angular/core';
|
|
8
8
|
import { WA_LOCAL_STORAGE, WA_USER_AGENT, WA_WINDOW } from '@ng-web-apis/common';
|
|
9
9
|
import { filterByKey, toValue, WA_STORAGE_EVENT, WaStorageService } from '@ng-web-apis/storage';
|
|
10
10
|
import { isPlatformBrowser, isPlatformServer, DOCUMENT, formatDate } from '@angular/common';
|
|
11
11
|
import * as i1$1 from '@angular/common/http';
|
|
12
|
-
import { HttpContextToken, HttpContext, HttpHeaders, HttpClient, HttpParams, HttpErrorResponse, HttpResponse } from '@angular/common/http';
|
|
12
|
+
import { HttpContextToken, HttpContext, HttpHeaders, HttpClient, HttpParams, HttpErrorResponse, HttpResponse, FetchBackend, HttpXhrBackend, HttpStatusCode, HttpBackend } from '@angular/common/http';
|
|
13
13
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
14
14
|
import { tuiIsPresent, TuiDay, TUI_TRUE_HANDLER } from '@taiga-ui/cdk';
|
|
15
15
|
import { tuiCleanObject } from '@taiga-ui/addon-doc/utils';
|
|
@@ -18,6 +18,21 @@ import { Router, NavigationEnd, ActivatedRoute } from '@angular/router';
|
|
|
18
18
|
import { toSnake, objectToSnake, objectToCamel } from 'ts-case-convert';
|
|
19
19
|
import { Meta, Title } from '@angular/platform-browser';
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Статусы HTTP-редиректа, отдаваемые при рендере на сервере (SSR).
|
|
23
|
+
*/
|
|
24
|
+
var ScRedirectStatusEnum;
|
|
25
|
+
(function (ScRedirectStatusEnum) {
|
|
26
|
+
/**
|
|
27
|
+
* Постоянный редирект: адрес изменился навсегда, страница заменяется в индексе поисковых систем.
|
|
28
|
+
*/
|
|
29
|
+
ScRedirectStatusEnum[ScRedirectStatusEnum["Permanent"] = 301] = "Permanent";
|
|
30
|
+
/**
|
|
31
|
+
* Временный редирект: исходный адрес может снова стать доступным, поэтому остаётся в индексе поисковых систем.
|
|
32
|
+
*/
|
|
33
|
+
ScRedirectStatusEnum[ScRedirectStatusEnum["Temporary"] = 302] = "Temporary";
|
|
34
|
+
})(ScRedirectStatusEnum || (ScRedirectStatusEnum = {}));
|
|
35
|
+
|
|
21
36
|
/**
|
|
22
37
|
* Статусы состояния запроса.
|
|
23
38
|
*/
|
|
@@ -461,6 +476,15 @@ const SC_AUTH_AS_CLIENT_OPTIONS = new InjectionToken('SC_AUTH_AS_CLIENT_OPTIONS'
|
|
|
461
476
|
*/
|
|
462
477
|
const SC_CACHE_LIFETIME = new InjectionToken('ICacheLifetime');
|
|
463
478
|
|
|
479
|
+
/**
|
|
480
|
+
* Токен, определяющий, что включён постоянный (301) редирект на канонический адрес товара или категории.
|
|
481
|
+
*
|
|
482
|
+
* Если механизм выключен, редирект выполняется навигацией внутри приложения, и SSR отвечает кодом 200.
|
|
483
|
+
*/
|
|
484
|
+
const SC_CANONICAL_REDIRECT_ENABLED = new InjectionToken('SC_CANONICAL_REDIRECT_ENABLED', {
|
|
485
|
+
factory: () => true,
|
|
486
|
+
});
|
|
487
|
+
|
|
464
488
|
/**
|
|
465
489
|
* Токен для получения полной информации о компании.
|
|
466
490
|
*/
|
|
@@ -1044,7 +1068,8 @@ class ScAuthService {
|
|
|
1044
1068
|
this.tokenService.removeGuestToken();
|
|
1045
1069
|
return of(null);
|
|
1046
1070
|
}
|
|
1047
|
-
|
|
1071
|
+
// На сервере ключ Гостя некуда сохранить, поэтому запрос не выполняется.
|
|
1072
|
+
return this.isServer ? of(null) : this.getGuestToken$();
|
|
1048
1073
|
}), takeUntilDestroyed(this.destroyRef))
|
|
1049
1074
|
.subscribe();
|
|
1050
1075
|
if (this.isServer) {
|
|
@@ -2053,10 +2078,6 @@ class ScUserMetadata {
|
|
|
2053
2078
|
* Формат отображения каталога.
|
|
2054
2079
|
*/
|
|
2055
2080
|
this.catalogFormat = ScCatalogFormat.normal;
|
|
2056
|
-
/**
|
|
2057
|
-
* Признак свёрнутой до значков боковой панели каталога.
|
|
2058
|
-
*/
|
|
2059
|
-
this.isCatalogSidebarCollapsed = false;
|
|
2060
2081
|
Object.assign(this, data);
|
|
2061
2082
|
}
|
|
2062
2083
|
}
|
|
@@ -3603,7 +3624,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
|
|
|
3603
3624
|
}] }, { type: ScUserMetrikaService }] });
|
|
3604
3625
|
|
|
3605
3626
|
/**
|
|
3606
|
-
* Сервис
|
|
3627
|
+
* Сервис редиректов каталога: на родительскую категорию при недоступности (HTTP 404) товара или категории
|
|
3628
|
+
* и на канонический адрес товара или категории (см. {@link redirectToCanonicalUrl}).
|
|
3629
|
+
*
|
|
3630
|
+
* Редирект на родительскую категорию.
|
|
3607
3631
|
*
|
|
3608
3632
|
* Запрашивает у бэкенда родительскую категорию недоступной сущности и выполняет редирект:
|
|
3609
3633
|
* - на сервере (SSR, RenderMode.Server) при доступном `RESPONSE_INIT` — выставляет HTTP-статус 302 и заголовок `Location` для корректного редиректа поисковых ботов;
|
|
@@ -3627,6 +3651,10 @@ class ScCatalogRedirectService {
|
|
|
3627
3651
|
* Признак использования идентификатора или slug в маршрутах товаров и категорий.
|
|
3628
3652
|
*/
|
|
3629
3653
|
this.idOrSlug = inject(SC_ID_OR_SLUG_IN_ROUTE);
|
|
3654
|
+
/**
|
|
3655
|
+
* Признак, что включён постоянный редирект на канонический адрес товара или категории.
|
|
3656
|
+
*/
|
|
3657
|
+
this.isCanonicalRedirectEnabled = inject(SC_CANONICAL_REDIRECT_ENABLED);
|
|
3630
3658
|
/**
|
|
3631
3659
|
* Признак, что текущий скрипт исполняется на сервере.
|
|
3632
3660
|
*/
|
|
@@ -3643,6 +3671,40 @@ class ScCatalogRedirectService {
|
|
|
3643
3671
|
*/
|
|
3644
3672
|
this.document = inject(DOCUMENT);
|
|
3645
3673
|
}
|
|
3674
|
+
/**
|
|
3675
|
+
* Возвращает результат стража маршрута для перехода на канонический адрес товара или категории
|
|
3676
|
+
* (адрес со slug либо с идентификатором — в зависимости от настроек окружения).
|
|
3677
|
+
*
|
|
3678
|
+
* @param commands Сегменты канонического маршрута.
|
|
3679
|
+
* @param status Статус редиректа: вид адреса задаётся настройками окружения и может быть изменён, поэтому по умолчанию временный.
|
|
3680
|
+
* @returns Дерево канонического маршрута для навигации либо `false`, если редирект выполнен ответом сервера.
|
|
3681
|
+
*/
|
|
3682
|
+
// eslint-disable-next-line sonarjs/function-return-type
|
|
3683
|
+
redirectToCanonicalUrl(commands, status = ScRedirectStatusEnum.Temporary) {
|
|
3684
|
+
const urlTree = this.router.createUrlTree(commands);
|
|
3685
|
+
if (this.isCanonicalRedirectEnabled && this.isServer && this.setServerRedirect(this.router.serializeUrl(urlTree), status)) {
|
|
3686
|
+
return false;
|
|
3687
|
+
}
|
|
3688
|
+
return urlTree;
|
|
3689
|
+
}
|
|
3690
|
+
/**
|
|
3691
|
+
* Выставляет редирект в ответе сервера (SSR).
|
|
3692
|
+
*
|
|
3693
|
+
* @param targetUrl Адрес, на который выполняется редирект.
|
|
3694
|
+
* @param status Статус редиректа.
|
|
3695
|
+
* @returns Признак, что редирект выставлен; `false`, если инициализатор ответа недоступен (Client/Prerender).
|
|
3696
|
+
*/
|
|
3697
|
+
setServerRedirect(targetUrl, status) {
|
|
3698
|
+
if (!this.responseInit) {
|
|
3699
|
+
return false;
|
|
3700
|
+
}
|
|
3701
|
+
this.responseInit.status = status;
|
|
3702
|
+
// responseInit.headers — инстанс Headers, заголовок ставим через .set().
|
|
3703
|
+
const headers = this.responseInit.headers instanceof Headers ? this.responseInit.headers : new Headers(this.responseInit.headers);
|
|
3704
|
+
headers.set('Location', targetUrl);
|
|
3705
|
+
this.responseInit.headers = headers;
|
|
3706
|
+
return true;
|
|
3707
|
+
}
|
|
3646
3708
|
/**
|
|
3647
3709
|
* Выполняет редирект на родительскую категорию недоступной категории.
|
|
3648
3710
|
*
|
|
@@ -3724,13 +3786,8 @@ class ScCatalogRedirectService {
|
|
|
3724
3786
|
const urlTree = this.router.createUrlTree(['/catalog', target]);
|
|
3725
3787
|
const targetUrl = this.router.serializeUrl(urlTree);
|
|
3726
3788
|
if (this.isServer) {
|
|
3727
|
-
//
|
|
3728
|
-
|
|
3729
|
-
if (this.responseInit) {
|
|
3730
|
-
this.responseInit.status = 302;
|
|
3731
|
-
const headers = this.responseInit.headers instanceof Headers ? this.responseInit.headers : new Headers(this.responseInit.headers);
|
|
3732
|
-
headers.set('Location', targetUrl);
|
|
3733
|
-
this.responseInit.headers = headers;
|
|
3789
|
+
// Скрытые категория или товар могут снова стать доступными, поэтому редирект на родителя временный.
|
|
3790
|
+
if (this.setServerRedirect(targetUrl, ScRedirectStatusEnum.Temporary)) {
|
|
3734
3791
|
return EMPTY;
|
|
3735
3792
|
}
|
|
3736
3793
|
// SSR в режиме Client/Prerender (RESPONSE_INIT отсутствует): серверный редирект невозможен —
|
|
@@ -5692,6 +5749,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
|
|
|
5692
5749
|
/**
|
|
5693
5750
|
* Страж выполняющий проверку маршрута товара и категории, и редиректа в зависимости от настроек окружения.
|
|
5694
5751
|
*
|
|
5752
|
+
* Редирект выполняет {@link ScCatalogRedirectService.redirectToCanonicalUrl}: на сервере (SSR) это постоянный
|
|
5753
|
+
* редирект (HTTP 301), в браузере — навигация внутри приложения.
|
|
5754
|
+
*
|
|
5695
5755
|
* @param route Сервис предоставляющий доступ к данным о маршруте.
|
|
5696
5756
|
*/
|
|
5697
5757
|
// eslint-disable-next-line sonarjs/function-return-type
|
|
@@ -5700,20 +5760,20 @@ const ScIdOrSlugGuard = (route) => {
|
|
|
5700
5760
|
const categoryIdOrSlug = paramMap.get('categoryIdOrSlug');
|
|
5701
5761
|
const productIdOrSlug = paramMap.get('productIdOrSlug');
|
|
5702
5762
|
const idOrSlug = inject(SC_ID_OR_SLUG_IN_ROUTE);
|
|
5703
|
-
const
|
|
5763
|
+
const catalogRedirectService = inject(ScCatalogRedirectService);
|
|
5704
5764
|
if (categoryIdOrSlug && /^\d+$/.test(categoryIdOrSlug) !== (idOrSlug === 'id')) {
|
|
5705
5765
|
return inject(ScCatalogService)
|
|
5706
5766
|
.getCategoryCached$(categoryIdOrSlug)
|
|
5707
5767
|
.pipe(filter(tuiIsPresent),
|
|
5708
5768
|
// eslint-disable-next-line security/detect-object-injection
|
|
5709
|
-
map$1((category) =>
|
|
5769
|
+
map$1((category) => catalogRedirectService.redirectToCanonicalUrl(['/catalog', category[idOrSlug]])), catchError$1(() => of(TUI_TRUE_HANDLER())));
|
|
5710
5770
|
}
|
|
5711
5771
|
if (productIdOrSlug && /^\d+$/.test(productIdOrSlug) !== (idOrSlug === 'id')) {
|
|
5712
5772
|
return inject(ScCatalogService)
|
|
5713
5773
|
.getProductData$(productIdOrSlug)
|
|
5714
5774
|
.pipe(filter(tuiIsPresent),
|
|
5715
5775
|
// eslint-disable-next-line security/detect-object-injection
|
|
5716
|
-
map$1((product) =>
|
|
5776
|
+
map$1((product) => catalogRedirectService.redirectToCanonicalUrl(['/catalog', 'product', product[idOrSlug]])), catchError$1(() => of(TUI_TRUE_HANDLER())));
|
|
5717
5777
|
}
|
|
5718
5778
|
return true;
|
|
5719
5779
|
};
|
|
@@ -8627,6 +8687,220 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
|
|
|
8627
8687
|
}]
|
|
8628
8688
|
}] });
|
|
8629
8689
|
|
|
8690
|
+
/**
|
|
8691
|
+
* Кэш ответов API, один на процесс: переживает пересоздание инжектора между SSR-рендерами.
|
|
8692
|
+
* Одинаковые одновременные запросы склеиваются, при переполнении вытесняется самая давняя запись.
|
|
8693
|
+
*/
|
|
8694
|
+
class ScSsrResponseCache {
|
|
8695
|
+
/**
|
|
8696
|
+
* Инициализирует экземпляр класса {@link ScSsrResponseCache}.
|
|
8697
|
+
*
|
|
8698
|
+
* @param maxEntries Максимальное количество записей в кэше.
|
|
8699
|
+
*/
|
|
8700
|
+
constructor(maxEntries = 100) {
|
|
8701
|
+
/**
|
|
8702
|
+
* Закэшированные ответы по ключу запроса.
|
|
8703
|
+
*/
|
|
8704
|
+
this.entries = new Map();
|
|
8705
|
+
/**
|
|
8706
|
+
* Выполняемые запросы по ключу запроса.
|
|
8707
|
+
*/
|
|
8708
|
+
this.pending = new Map();
|
|
8709
|
+
/**
|
|
8710
|
+
* Количество запросов, отданных без обращения к API.
|
|
8711
|
+
*/
|
|
8712
|
+
this.hits = 0;
|
|
8713
|
+
/**
|
|
8714
|
+
* Количество запросов, выполненных к API.
|
|
8715
|
+
*/
|
|
8716
|
+
this.misses = 0;
|
|
8717
|
+
this.maxEntries = Number.isFinite(maxEntries) ? Math.max(1, maxEntries) : 100;
|
|
8718
|
+
}
|
|
8719
|
+
/**
|
|
8720
|
+
* Возвращает ответ из кэша, при отсутствии или устаревании загружает и кэширует. Ошибка загрузки не кэшируется.
|
|
8721
|
+
*
|
|
8722
|
+
* @param key Ключ запроса.
|
|
8723
|
+
* @param lifeTime Время жизни ответа (мс).
|
|
8724
|
+
* @param loader Функция загрузки ответа.
|
|
8725
|
+
*/
|
|
8726
|
+
getOrLoad(key, lifeTime, loader) {
|
|
8727
|
+
const cached = this.get(key);
|
|
8728
|
+
if (cached) {
|
|
8729
|
+
this.hits += 1;
|
|
8730
|
+
return Promise.resolve(cached.response);
|
|
8731
|
+
}
|
|
8732
|
+
const pending = this.pending.get(key);
|
|
8733
|
+
if (pending) {
|
|
8734
|
+
this.hits += 1;
|
|
8735
|
+
return pending;
|
|
8736
|
+
}
|
|
8737
|
+
this.misses += 1;
|
|
8738
|
+
const loading = loader()
|
|
8739
|
+
.then((response) => {
|
|
8740
|
+
this.set(key, { response, expiresAt: Date.now() + lifeTime });
|
|
8741
|
+
return response;
|
|
8742
|
+
})
|
|
8743
|
+
.finally(() => {
|
|
8744
|
+
this.pending.delete(key);
|
|
8745
|
+
});
|
|
8746
|
+
this.pending.set(key, loading);
|
|
8747
|
+
return loading;
|
|
8748
|
+
}
|
|
8749
|
+
/**
|
|
8750
|
+
* Возвращает статистику работы кэша.
|
|
8751
|
+
*/
|
|
8752
|
+
getStats() {
|
|
8753
|
+
return { size: this.entries.size, hits: this.hits, misses: this.misses };
|
|
8754
|
+
}
|
|
8755
|
+
/**
|
|
8756
|
+
* Возвращает актуальную запись или `null`.
|
|
8757
|
+
*
|
|
8758
|
+
* @param key Ключ запроса.
|
|
8759
|
+
*/
|
|
8760
|
+
get(key) {
|
|
8761
|
+
const entry = this.entries.get(key);
|
|
8762
|
+
if (!entry) {
|
|
8763
|
+
return null;
|
|
8764
|
+
}
|
|
8765
|
+
if (entry.expiresAt <= Date.now()) {
|
|
8766
|
+
this.entries.delete(key);
|
|
8767
|
+
return null;
|
|
8768
|
+
}
|
|
8769
|
+
// Перестановка в конец помечает запись как недавно использованную.
|
|
8770
|
+
this.entries.delete(key);
|
|
8771
|
+
this.entries.set(key, entry);
|
|
8772
|
+
return entry;
|
|
8773
|
+
}
|
|
8774
|
+
/**
|
|
8775
|
+
* Сохраняет запись, при переполнении вытесняя самую давнюю.
|
|
8776
|
+
*
|
|
8777
|
+
* @param key Ключ запроса.
|
|
8778
|
+
* @param entry Запись кэша.
|
|
8779
|
+
*/
|
|
8780
|
+
set(key, entry) {
|
|
8781
|
+
this.entries.delete(key);
|
|
8782
|
+
if (this.entries.size >= this.maxEntries) {
|
|
8783
|
+
const [oldestKey] = this.entries.keys();
|
|
8784
|
+
this.entries.delete(oldestKey);
|
|
8785
|
+
}
|
|
8786
|
+
this.entries.set(key, entry);
|
|
8787
|
+
}
|
|
8788
|
+
}
|
|
8789
|
+
|
|
8790
|
+
/**
|
|
8791
|
+
* Кэш по умолчанию, один на процесс.
|
|
8792
|
+
*/
|
|
8793
|
+
const defaultSsrResponseCache = new ScSsrResponseCache();
|
|
8794
|
+
/**
|
|
8795
|
+
* Токен кэша ответов API между SSR-рендерами. По умолчанию один экземпляр на процесс, можно переопределить провайдером.
|
|
8796
|
+
*/
|
|
8797
|
+
const SC_SSR_RESPONSE_CACHE = new InjectionToken('SC_SSR_RESPONSE_CACHE', {
|
|
8798
|
+
factory: () => defaultSsrResponseCache,
|
|
8799
|
+
});
|
|
8800
|
+
|
|
8801
|
+
/**
|
|
8802
|
+
* Токен настроек кэша ответов API между SSR-рендерами. По умолчанию кэш выключен.
|
|
8803
|
+
*/
|
|
8804
|
+
const SC_SSR_RESPONSE_CACHE_SETTINGS = new InjectionToken('SC_SSR_RESPONSE_CACHE_SETTINGS', {
|
|
8805
|
+
factory: () => ({ endpoints: [], lifeTime: 0, loadTimeout: 0 }),
|
|
8806
|
+
});
|
|
8807
|
+
|
|
8808
|
+
/**
|
|
8809
|
+
* Возвращает признак, что ответ можно кэшировать между SSR-рендерами: GET за JSON без ключей доступа по адресам из настроек.
|
|
8810
|
+
*
|
|
8811
|
+
* @param request Данные запроса.
|
|
8812
|
+
* @param settings Настройки кэша.
|
|
8813
|
+
* @param apiUrl Адрес API.
|
|
8814
|
+
* @param apiKeys Названия заголовков ключей доступа к API.
|
|
8815
|
+
*
|
|
8816
|
+
* @internal
|
|
8817
|
+
*/
|
|
8818
|
+
function isSsrResponseCacheable(request, settings, apiUrl, apiKeys) {
|
|
8819
|
+
if (!Number.isFinite(settings.lifeTime) || settings.lifeTime <= 0 || request.method !== 'GET' || request.responseType !== 'json') {
|
|
8820
|
+
return false;
|
|
8821
|
+
}
|
|
8822
|
+
if (request.headers.has(apiKeys.keyName) || request.headers.has(apiKeys.guestKeyName)) {
|
|
8823
|
+
return false;
|
|
8824
|
+
}
|
|
8825
|
+
const [url] = request.url.split('?');
|
|
8826
|
+
if (!url.startsWith(apiUrl)) {
|
|
8827
|
+
return false;
|
|
8828
|
+
}
|
|
8829
|
+
const path = url.slice(apiUrl.length);
|
|
8830
|
+
return settings.endpoints.some((endpoint) => (endpoint.endsWith('*') ? path.startsWith(endpoint.slice(0, -1)) : path === endpoint));
|
|
8831
|
+
}
|
|
8832
|
+
|
|
8833
|
+
/**
|
|
8834
|
+
* Обработчик запросов с кэшем ответов API между SSR-рендерами.
|
|
8835
|
+
*
|
|
8836
|
+
* Стоит на уровне {@link HttpBackend}, ниже перехватчиков: ответ из кэша проходит через перенос в браузер,
|
|
8837
|
+
* а проверка ключей доступа видит итоговые заголовки. Отдаёт только итоговый {@link HttpResponse};
|
|
8838
|
+
* запрос не отменяется при отписке рендера, а ограничен `loadTimeout` из настроек.
|
|
8839
|
+
*/
|
|
8840
|
+
class ScSsrResponseCacheBackend {
|
|
8841
|
+
constructor() {
|
|
8842
|
+
/**
|
|
8843
|
+
* Штатный обработчик, выполняющий запросы к API.
|
|
8844
|
+
*/
|
|
8845
|
+
// TODO: Angular 21: проверить, какой backend регистрирует provideHttpClient.
|
|
8846
|
+
this.backend = inject(FetchBackend, { optional: true }) ?? inject(HttpXhrBackend);
|
|
8847
|
+
/**
|
|
8848
|
+
* Кэш ответов API.
|
|
8849
|
+
*/
|
|
8850
|
+
this.cache = inject(SC_SSR_RESPONSE_CACHE);
|
|
8851
|
+
/**
|
|
8852
|
+
* Настройки кэша.
|
|
8853
|
+
*/
|
|
8854
|
+
this.settings = inject(SC_SSR_RESPONSE_CACHE_SETTINGS);
|
|
8855
|
+
/**
|
|
8856
|
+
* Адрес API.
|
|
8857
|
+
*/
|
|
8858
|
+
this.apiUrl = inject(SC_URLS).apiUrl;
|
|
8859
|
+
/**
|
|
8860
|
+
* Названия заголовков ключей доступа к API.
|
|
8861
|
+
*/
|
|
8862
|
+
this.apiKeys = inject(SC_API_KEYS);
|
|
8863
|
+
}
|
|
8864
|
+
/** @inheritDoc */
|
|
8865
|
+
handle(request) {
|
|
8866
|
+
if (!isSsrResponseCacheable(request, this.settings, this.apiUrl, this.apiKeys)) {
|
|
8867
|
+
return this.backend.handle(request);
|
|
8868
|
+
}
|
|
8869
|
+
return defer(() => from(this.cache.getOrLoad(request.urlWithParams, this.settings.lifeTime, () => this.load(request)))).pipe(
|
|
8870
|
+
// Копия тела на каждый рендер, чтобы изменения не утекали между рендерами.
|
|
8871
|
+
map$1((response) => response.clone({ body: structuredClone(response.body) })));
|
|
8872
|
+
}
|
|
8873
|
+
/**
|
|
8874
|
+
* Выполняет запрос к API и возвращает его ответ.
|
|
8875
|
+
*
|
|
8876
|
+
* @param request Данные запроса.
|
|
8877
|
+
*/
|
|
8878
|
+
load(request) {
|
|
8879
|
+
const response$ = this.backend.handle(request).pipe(filter((event) => event instanceof HttpResponse));
|
|
8880
|
+
if (this.settings.loadTimeout <= 0) {
|
|
8881
|
+
return lastValueFrom(response$);
|
|
8882
|
+
}
|
|
8883
|
+
// По таймауту запрос отменяется, ошибка отдаётся как HttpErrorResponse, как и остальные ошибки запросов.
|
|
8884
|
+
return lastValueFrom(response$.pipe(timeout(this.settings.loadTimeout), catchError$1((error) => throwError(() => error instanceof TimeoutError
|
|
8885
|
+
? new HttpErrorResponse({ status: HttpStatusCode.GatewayTimeout, statusText: 'Gateway Timeout', url: request.url, error })
|
|
8886
|
+
: error))));
|
|
8887
|
+
}
|
|
8888
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: ScSsrResponseCacheBackend, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
8889
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: ScSsrResponseCacheBackend }); }
|
|
8890
|
+
}
|
|
8891
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: ScSsrResponseCacheBackend, decorators: [{
|
|
8892
|
+
type: Injectable
|
|
8893
|
+
}] });
|
|
8894
|
+
|
|
8895
|
+
/**
|
|
8896
|
+
* Возвращает провайдеры кэша ответов API между SSR-рендерами.
|
|
8897
|
+
* Подключать только в серверной конфигурации после `provideHttpClient()`: подменяют {@link HttpBackend}.
|
|
8898
|
+
*/
|
|
8899
|
+
// TODO: Angular 21: проверить, что подмена HttpBackend остаётся последней после provideServerRendering(withRoutes()).
|
|
8900
|
+
function provideSsrResponseCache() {
|
|
8901
|
+
return makeEnvironmentProviders([{ provide: HttpBackend, useClass: ScSsrResponseCacheBackend }]);
|
|
8902
|
+
}
|
|
8903
|
+
|
|
8630
8904
|
/**
|
|
8631
8905
|
* Перечисление типов подсказок запросов.
|
|
8632
8906
|
*/
|
|
@@ -9006,5 +9280,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
|
|
|
9006
9280
|
* Generated bundle index. Do not edit.
|
|
9007
9281
|
*/
|
|
9008
9282
|
|
|
9009
|
-
export { CATEGORY_PROVIDERS, EMPTY_CART, IS_BROWSER, IS_RUNNING_ON_TERMINAL, IS_SERVER, SC_ACCESS_AUTH_TOKEN_STORAGE_KEY, SC_API_KEYS, SC_AUTH_ADD_HEADER_REQUIRED, SC_AUTH_AS_CLIENT_DEFAULT_OPTIONS, SC_AUTH_AS_CLIENT_OPTIONS, SC_CACHE_LIFETIME, SC_CACHE_SETTINGS, SC_CATALOG_ITEM_PAGE_META, SC_CATALOG_ROOT_PAGE_META, SC_CATEGORY_INFO, SC_CATEGORY_STATE, SC_COMPANY_INFO, SC_COMPANY_NAME, SC_CONFIGURATOR_COMPONENTS, SC_CONFIGURATOR_SETTINGS, SC_COUNT_FIRST_DISCOUNTED_PRODUCTS, SC_COUNT_LAST_NEWS, SC_DATE_FORMAT, SC_DEFAULT_PAGE_META, SC_FEEDBACK_API, SC_GUEST_ENDPOINTS_TOKEN, SC_GUEST_PARAMETER_NAME_TOKEN, SC_GUEST_TOKEN_STORAGE_KEY, SC_ID_OR_SLUG_IN_ROUTE, SC_IS_HEADER_REQUIRED, SC_IS_HIDDEN_ERROR_ALERT, SC_IS_HIDDEN_ERROR_ALERT_HTTP_CONTEXT, SC_IS_LOGOUT_REQUEST, SC_IS_REFRESH_REQUIRED, SC_LINEAR_VALUES, SC_LINEAR_VALUES_TOKEN, SC_MAX_LENGTH_SEARCH_TERM, SC_MIN_LENGTH_SEARCH_TERM, SC_NEXT_PAGE_PAGINATION_CLICK, SC_ORDER_LOADER, SC_ORDER_LOADING, SC_PAGE_QUERY_PARAM, SC_PATH_IMAGE_NOT_FOUND, SC_PRODUCTS_PAGINATION_LOADER, SC_PRODUCT_PAGINATION_DEFAULT_OPTIONS, SC_PRODUCT_PAGINATION_OPTIONS, SC_REFRESH_AUTH_TOKEN_STORAGE_KEY, SC_RELEASE, SC_SSR_HTTP_TIMEOUT, SC_UPDATE_INTERVAL, SC_URLS, SC_USER_INFO, SC_VACANCIES_DATA_SOURCE, SC_VIRTUAL_CATEGORY_INFO, SC_VIRTUAL_CATEGORY_PROVIDERS, SEARCH_TERM, SEARCH_TERM_PROVIDERS, ScAuthAsClientGuard, ScAuthInterceptor, ScAuthService, ScBanner, ScBannerService, ScCacheInterceptor, ScCacheSettings, ScCachedData, ScCachedItem, ScCart, ScCartService, ScCatalogFilterService, ScCatalogFormat, ScCatalogItem, ScCatalogRedirectService, ScCatalogService, ScCatalogableItem, ScCategory, ScClientType, ScCodedIdentity, ScConfiguratorService, ScContactsService, ScContragentService, ScConvertInterceptor, ScConvertersService, ScDateFormatInterceptor, ScDeletableNamedIdentity, ScDeliveryAddressService, ScDeliveryCost, ScDeliveryType, ScDocumentInfoNode, ScDocumentInfoTypesEnum, ScErrorRequestState, ScErrorsInterceptor, ScFavoriteService, ScFeedbackForms, ScFeedbackService, ScFilesService, ScFrequentlyAskedQuestionsService, ScGuestInterceptor, ScHiddenCatalogableItem, ScIBreadcrumbItem, ScISuggestionType, ScIconTypesEnum, ScIdOrSlugGuard, ScIdOrSlugPipe, ScIdentity, ScImage, ScImageHelper, ScLoadingRequestState, ScLocationsService, ScMediaImageTransformerPipe, ScMimeTypes, ScNamedIdentity, ScNews, ScNewsService, ScNotificationActionTypes, ScNotificationLevelNames, ScNotificationsService, ScOpfList, ScOptionsInterceptor, ScOrder, ScOrderBase, ScOrderDraft, ScOrderDraftShort, ScOrderDraftType, ScOrderDraftsService, ScOrderItem, ScOrderShort, ScOrderStateStatus, ScOrdersService, ScPaginationPageQueryParamService, ScPaginationService, ScPaymentStatus, ScPaymentType, ScPhoneService, ScPrimaryCatalogableItem, ScProduct, ScProductTileType, ScQuestionnaireService, ScQuestionnaireStatusEnum, ScReclamationService, ScReclamationStatus, ScRecommendationService, ScReference, ScReferenceName, ScReferencesService, ScRequestStateStatusEnum, ScRequisitesService, ScRouteKeys, ScSearchService, ScSeoService, ScSocialType, ScSsrTimeoutInterceptor, ScSuccessRequestState, ScSuggestionService, ScSum, ScTokenService, ScUIService, ScUTMService, ScUnitsHelper, ScUpdatableNamedIdentity, ScUploadedFile, ScUser, ScUserMetadata, ScUserMetrikaGoalsEnum, ScUserMetrikaService, ScUserService, ScUserType, ScVCardService, ScVacanciesList, ScVacanciesService, ScVacancy, ScVerificationService, ScVirtualCategory, ScWarehouseService, SchemaOrgFactory, TERMINAL_PROVIDERS, USER_AGENT_TERMINAL, filterChangedByKey, loadingPlaceholder, mapRequestStateToDataOrNull, mapToRequestState, runningOnTerminalFactory, scGetCurrentRoute, scGetPageQueryParamChanges, scOrderIsLoaded, scParsePageQueryParam, searchTermFactory };
|
|
9283
|
+
export { CATEGORY_PROVIDERS, EMPTY_CART, IS_BROWSER, IS_RUNNING_ON_TERMINAL, IS_SERVER, SC_ACCESS_AUTH_TOKEN_STORAGE_KEY, SC_API_KEYS, SC_AUTH_ADD_HEADER_REQUIRED, SC_AUTH_AS_CLIENT_DEFAULT_OPTIONS, SC_AUTH_AS_CLIENT_OPTIONS, SC_CACHE_LIFETIME, SC_CACHE_SETTINGS, SC_CANONICAL_REDIRECT_ENABLED, SC_CATALOG_ITEM_PAGE_META, SC_CATALOG_ROOT_PAGE_META, SC_CATEGORY_INFO, SC_CATEGORY_STATE, SC_COMPANY_INFO, SC_COMPANY_NAME, SC_CONFIGURATOR_COMPONENTS, SC_CONFIGURATOR_SETTINGS, SC_COUNT_FIRST_DISCOUNTED_PRODUCTS, SC_COUNT_LAST_NEWS, SC_DATE_FORMAT, SC_DEFAULT_PAGE_META, SC_FEEDBACK_API, SC_GUEST_ENDPOINTS_TOKEN, SC_GUEST_PARAMETER_NAME_TOKEN, SC_GUEST_TOKEN_STORAGE_KEY, SC_ID_OR_SLUG_IN_ROUTE, SC_IS_HEADER_REQUIRED, SC_IS_HIDDEN_ERROR_ALERT, SC_IS_HIDDEN_ERROR_ALERT_HTTP_CONTEXT, SC_IS_LOGOUT_REQUEST, SC_IS_REFRESH_REQUIRED, SC_LINEAR_VALUES, SC_LINEAR_VALUES_TOKEN, SC_MAX_LENGTH_SEARCH_TERM, SC_MIN_LENGTH_SEARCH_TERM, SC_NEXT_PAGE_PAGINATION_CLICK, SC_ORDER_LOADER, SC_ORDER_LOADING, SC_PAGE_QUERY_PARAM, SC_PATH_IMAGE_NOT_FOUND, SC_PRODUCTS_PAGINATION_LOADER, SC_PRODUCT_PAGINATION_DEFAULT_OPTIONS, SC_PRODUCT_PAGINATION_OPTIONS, SC_REFRESH_AUTH_TOKEN_STORAGE_KEY, SC_RELEASE, SC_SSR_HTTP_TIMEOUT, SC_SSR_RESPONSE_CACHE, SC_SSR_RESPONSE_CACHE_SETTINGS, SC_UPDATE_INTERVAL, SC_URLS, SC_USER_INFO, SC_VACANCIES_DATA_SOURCE, SC_VIRTUAL_CATEGORY_INFO, SC_VIRTUAL_CATEGORY_PROVIDERS, SEARCH_TERM, SEARCH_TERM_PROVIDERS, ScAuthAsClientGuard, ScAuthInterceptor, ScAuthService, ScBanner, ScBannerService, ScCacheInterceptor, ScCacheSettings, ScCachedData, ScCachedItem, ScCart, ScCartService, ScCatalogFilterService, ScCatalogFormat, ScCatalogItem, ScCatalogRedirectService, ScCatalogService, ScCatalogableItem, ScCategory, ScClientType, ScCodedIdentity, ScConfiguratorService, ScContactsService, ScContragentService, ScConvertInterceptor, ScConvertersService, ScDateFormatInterceptor, ScDeletableNamedIdentity, ScDeliveryAddressService, ScDeliveryCost, ScDeliveryType, ScDocumentInfoNode, ScDocumentInfoTypesEnum, ScErrorRequestState, ScErrorsInterceptor, ScFavoriteService, ScFeedbackForms, ScFeedbackService, ScFilesService, ScFrequentlyAskedQuestionsService, ScGuestInterceptor, ScHiddenCatalogableItem, ScIBreadcrumbItem, ScISuggestionType, ScIconTypesEnum, ScIdOrSlugGuard, ScIdOrSlugPipe, ScIdentity, ScImage, ScImageHelper, ScLoadingRequestState, ScLocationsService, ScMediaImageTransformerPipe, ScMimeTypes, ScNamedIdentity, ScNews, ScNewsService, ScNotificationActionTypes, ScNotificationLevelNames, ScNotificationsService, ScOpfList, ScOptionsInterceptor, ScOrder, ScOrderBase, ScOrderDraft, ScOrderDraftShort, ScOrderDraftType, ScOrderDraftsService, ScOrderItem, ScOrderShort, ScOrderStateStatus, ScOrdersService, ScPaginationPageQueryParamService, ScPaginationService, ScPaymentStatus, ScPaymentType, ScPhoneService, ScPrimaryCatalogableItem, ScProduct, ScProductTileType, ScQuestionnaireService, ScQuestionnaireStatusEnum, ScReclamationService, ScReclamationStatus, ScRecommendationService, ScRedirectStatusEnum, ScReference, ScReferenceName, ScReferencesService, ScRequestStateStatusEnum, ScRequisitesService, ScRouteKeys, ScSearchService, ScSeoService, ScSocialType, ScSsrResponseCache, ScSsrResponseCacheBackend, ScSsrTimeoutInterceptor, ScSuccessRequestState, ScSuggestionService, ScSum, ScTokenService, ScUIService, ScUTMService, ScUnitsHelper, ScUpdatableNamedIdentity, ScUploadedFile, ScUser, ScUserMetadata, ScUserMetrikaGoalsEnum, ScUserMetrikaService, ScUserService, ScUserType, ScVCardService, ScVacanciesList, ScVacanciesService, ScVacancy, ScVerificationService, ScVirtualCategory, ScWarehouseService, SchemaOrgFactory, TERMINAL_PROVIDERS, USER_AGENT_TERMINAL, filterChangedByKey, loadingPlaceholder, mapRequestStateToDataOrNull, mapToRequestState, provideSsrResponseCache, runningOnTerminalFactory, scGetCurrentRoute, scGetPageQueryParamChanges, scOrderIsLoaded, scParsePageQueryParam, searchTermFactory };
|
|
9010
9284
|
//# sourceMappingURL=snabcentr-client-core.mjs.map
|