@escapenavigator/hooks 1.10.153 → 1.10.154
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/dist/consent-checkboxes/build-consent-urls.d.ts +1 -1
- package/dist/consent-checkboxes/build-consent-urls.js +1 -1
- package/dist/consent-checkboxes/index.d.ts +1 -1
- package/dist/consent-checkboxes/index.js +1 -1
- package/dist/lazy-with-retry.d.ts +23 -3
- package/dist/lazy-with-retry.js +49 -9
- package/package.json +4 -4
|
@@ -21,5 +21,5 @@ export type ConsentUrls = {
|
|
|
21
21
|
* - агримент: `/company/:slug/terms-of-services` (пусто, если нет slug);
|
|
22
22
|
* - политика: `/documents/privacy_policy/:language`.
|
|
23
23
|
*/
|
|
24
|
-
export declare const buildConsentUrls: ({ baseUrl, slug, language }: BuildConsentUrlsParams) => ConsentUrls;
|
|
24
|
+
export declare const buildConsentUrls: ({ baseUrl, slug, language, }: BuildConsentUrlsParams) => ConsentUrls;
|
|
25
25
|
export {};
|
|
@@ -6,7 +6,7 @@ exports.buildConsentUrls = void 0;
|
|
|
6
6
|
* - агримент: `/company/:slug/terms-of-services` (пусто, если нет slug);
|
|
7
7
|
* - политика: `/documents/privacy_policy/:language`.
|
|
8
8
|
*/
|
|
9
|
-
const buildConsentUrls = ({ baseUrl, slug, language }) => {
|
|
9
|
+
const buildConsentUrls = ({ baseUrl, slug, language, }) => {
|
|
10
10
|
const base = (baseUrl || '').replace(/\/+$/, '');
|
|
11
11
|
const lang = language || 'en';
|
|
12
12
|
return {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { buildConsentUrls, ConsentUrls } from './build-consent-urls';
|
|
3
|
-
export { buildConsentUrls };
|
|
4
3
|
export type { ConsentUrls };
|
|
4
|
+
export { buildConsentUrls };
|
|
5
5
|
export type ConsentField = 'agreeTerms' | 'agreePolicy' | 'sendAds';
|
|
6
6
|
export type ConsentValues = Record<ConsentField, boolean>;
|
|
7
7
|
export declare const INITIAL_CONSENT_VALUES: ConsentValues;
|
|
@@ -19,7 +19,7 @@ exports.INITIAL_CONSENT_VALUES = {
|
|
|
19
19
|
/** Обязательные согласия отмечены — можно подтверждать заказ/подпись. */
|
|
20
20
|
const areRequiredConsentsAccepted = (values) => values.agreeTerms && values.agreePolicy;
|
|
21
21
|
exports.areRequiredConsentsAccepted = areRequiredConsentsAccepted;
|
|
22
|
-
const labelColor = (invalid) =>
|
|
22
|
+
const labelColor = (invalid) => invalid ? 'negative' : 'secondary';
|
|
23
23
|
const ConsentCheckboxes = ({ agreementUrl, policyUrl, values, onChange, hideMarketing, showRequiredError, }) => {
|
|
24
24
|
const { t } = (0, react_i18next_1.useTranslation)('common');
|
|
25
25
|
// Агримент + политику объединили в одну обязательную галочку: меньше
|
|
@@ -12,9 +12,18 @@
|
|
|
12
12
|
*
|
|
13
13
|
* Алгоритм:
|
|
14
14
|
* 1. Один retry с небольшой паузой — на случай флапа сети/прогрева CDN.
|
|
15
|
-
* 2. Если опять упало и похоже на «протухший чанк»
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* 2. Если опять упало и похоже на «протухший чанк» (или обрезанный файл,
|
|
16
|
+
* Safari: "Unexpected end of script") — один раз перезагружаем страницу
|
|
17
|
+
* с `?v=<ts>`, чтобы выкинуть HTTP-кэш. Флаг в sessionStorage защищает
|
|
18
|
+
* от циклической перезагрузки.
|
|
19
|
+
* 3. Если reload реально запущен — НЕ пробрасываем ошибку, а возвращаем
|
|
20
|
+
* вечный pending: Suspense продолжает показывать лоадер до перезагрузки.
|
|
21
|
+
* Иначе ErrorBoundary на мгновение мигает экраном ошибки и шлёт событие
|
|
22
|
+
* в Sentry, хотя апдейт «лечится» сам. Тихий reload = бесшовный апдейт.
|
|
23
|
+
* 4. Если reload УЖЕ был, а чанк всё равно не грузится (реально битый
|
|
24
|
+
* деплой) — кидаем `ChunkRecoveryFailedError`. Её формулировка намеренно
|
|
25
|
+
* не совпадает с `ignoreErrors`-паттернами протухших чанков, поэтому
|
|
26
|
+
* событие гарантированно доходит до Sentry (оригинал — в `cause`).
|
|
18
27
|
*/
|
|
19
28
|
import { ComponentType, lazy } from 'react';
|
|
20
29
|
/** Query-параметр cache-bust после reload; не должен попадать в `where` таблиц. */
|
|
@@ -24,6 +33,17 @@ export declare const CHUNK_RELOAD_QUERY_PARAM = "v";
|
|
|
24
33
|
* Вызываем до первого рендера и после успешной загрузки lazy-чанка.
|
|
25
34
|
*/
|
|
26
35
|
export declare const stripChunkReloadQueryParam: () => boolean;
|
|
36
|
+
/**
|
|
37
|
+
* «Чанк не поднялся даже после retry и cache-bust reload» — реально битый
|
|
38
|
+
* деплой / сборка. Текст и name НАМЕРЕННО не содержат браузерных формулировок
|
|
39
|
+
* протухшего чанка ("Failed to fetch dynamically imported module" и т.п.) —
|
|
40
|
+
* те стоят в Sentry `ignoreErrors`, а это событие обязано дойти до Sentry.
|
|
41
|
+
* Оригинальная ошибка — в `cause` (Sentry linkedErrors подцепит её в issue).
|
|
42
|
+
*/
|
|
43
|
+
export declare class ChunkRecoveryFailedError extends Error {
|
|
44
|
+
cause: unknown;
|
|
45
|
+
constructor(original: unknown);
|
|
46
|
+
}
|
|
27
47
|
export declare function lazyWithRetry<T extends ComponentType<any>>(factory: () => Promise<{
|
|
28
48
|
default: T;
|
|
29
49
|
}>): ReturnType<typeof lazy<T>>;
|
package/dist/lazy-with-retry.js
CHANGED
|
@@ -13,9 +13,18 @@
|
|
|
13
13
|
*
|
|
14
14
|
* Алгоритм:
|
|
15
15
|
* 1. Один retry с небольшой паузой — на случай флапа сети/прогрева CDN.
|
|
16
|
-
* 2. Если опять упало и похоже на «протухший чанк»
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
* 2. Если опять упало и похоже на «протухший чанк» (или обрезанный файл,
|
|
17
|
+
* Safari: "Unexpected end of script") — один раз перезагружаем страницу
|
|
18
|
+
* с `?v=<ts>`, чтобы выкинуть HTTP-кэш. Флаг в sessionStorage защищает
|
|
19
|
+
* от циклической перезагрузки.
|
|
20
|
+
* 3. Если reload реально запущен — НЕ пробрасываем ошибку, а возвращаем
|
|
21
|
+
* вечный pending: Suspense продолжает показывать лоадер до перезагрузки.
|
|
22
|
+
* Иначе ErrorBoundary на мгновение мигает экраном ошибки и шлёт событие
|
|
23
|
+
* в Sentry, хотя апдейт «лечится» сам. Тихий reload = бесшовный апдейт.
|
|
24
|
+
* 4. Если reload УЖЕ был, а чанк всё равно не грузится (реально битый
|
|
25
|
+
* деплой) — кидаем `ChunkRecoveryFailedError`. Её формулировка намеренно
|
|
26
|
+
* не совпадает с `ignoreErrors`-паттернами протухших чанков, поэтому
|
|
27
|
+
* событие гарантированно доходит до Sentry (оригинал — в `cause`).
|
|
19
28
|
*/
|
|
20
29
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
21
30
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
@@ -28,7 +37,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
28
37
|
});
|
|
29
38
|
};
|
|
30
39
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
31
|
-
exports.stripChunkReloadQueryParam = exports.CHUNK_RELOAD_QUERY_PARAM = void 0;
|
|
40
|
+
exports.ChunkRecoveryFailedError = exports.stripChunkReloadQueryParam = exports.CHUNK_RELOAD_QUERY_PARAM = void 0;
|
|
32
41
|
exports.lazyWithRetry = lazyWithRetry;
|
|
33
42
|
const is_stale_chunk_error_1 = require("@escapenavigator/utils/dist/is-stale-chunk-error");
|
|
34
43
|
const react_1 = require("react");
|
|
@@ -51,19 +60,42 @@ const stripChunkReloadQueryParam = () => {
|
|
|
51
60
|
return true;
|
|
52
61
|
};
|
|
53
62
|
exports.stripChunkReloadQueryParam = stripChunkReloadQueryParam;
|
|
63
|
+
/**
|
|
64
|
+
* «Чанк не поднялся даже после retry и cache-bust reload» — реально битый
|
|
65
|
+
* деплой / сборка. Текст и name НАМЕРЕННО не содержат браузерных формулировок
|
|
66
|
+
* протухшего чанка ("Failed to fetch dynamically imported module" и т.п.) —
|
|
67
|
+
* те стоят в Sentry `ignoreErrors`, а это событие обязано дойти до Sentry.
|
|
68
|
+
* Оригинальная ошибка — в `cause` (Sentry linkedErrors подцепит её в issue).
|
|
69
|
+
*/
|
|
70
|
+
class ChunkRecoveryFailedError extends Error {
|
|
71
|
+
constructor(original) {
|
|
72
|
+
super('Chunk recovery failed: retry and cache-bust reload did not help');
|
|
73
|
+
this.name = 'ChunkRecoveryFailedError';
|
|
74
|
+
this.cause = original;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
exports.ChunkRecoveryFailedError = ChunkRecoveryFailedError;
|
|
78
|
+
// Флаг «reload уже запущен на этой странице»: при апдейте может одновременно
|
|
79
|
+
// упасть несколько чанков (JS + CSS, параллельные route-чанки). Первый запускает
|
|
80
|
+
// reload, остальные не должны спамить ErrorBoundary/Sentry — страница и так умирает.
|
|
81
|
+
let reloadInitiatedOnThisPage = false;
|
|
54
82
|
const reloadOnce = () => {
|
|
83
|
+
if (reloadInitiatedOnThisPage)
|
|
84
|
+
return 'in-progress';
|
|
55
85
|
try {
|
|
56
86
|
if (sessionStorage.getItem(RELOAD_FLAG))
|
|
57
|
-
return;
|
|
87
|
+
return 'exhausted';
|
|
58
88
|
sessionStorage.setItem(RELOAD_FLAG, '1');
|
|
59
89
|
}
|
|
60
90
|
catch (_a) {
|
|
61
91
|
// sessionStorage может быть недоступен (Safari privacy / iframe);
|
|
62
92
|
// тогда просто перезагрузимся, лучше один лишний reload, чем белый экран.
|
|
63
93
|
}
|
|
94
|
+
reloadInitiatedOnThisPage = true;
|
|
64
95
|
const url = new URL(window.location.href);
|
|
65
96
|
url.searchParams.set(exports.CHUNK_RELOAD_QUERY_PARAM, Date.now().toString(36));
|
|
66
97
|
window.location.replace(url.toString());
|
|
98
|
+
return 'initiated';
|
|
67
99
|
};
|
|
68
100
|
function lazyWithRetry(factory) {
|
|
69
101
|
return (0, react_1.lazy)(() => __awaiter(this, void 0, void 0, function* () {
|
|
@@ -79,17 +111,25 @@ function lazyWithRetry(factory) {
|
|
|
79
111
|
return mod;
|
|
80
112
|
}
|
|
81
113
|
catch (firstError) {
|
|
82
|
-
if (!(0, is_stale_chunk_error_1.
|
|
114
|
+
if (!(0, is_stale_chunk_error_1.isRecoverableImportError)(firstError))
|
|
83
115
|
throw firstError;
|
|
84
116
|
try {
|
|
85
117
|
yield new Promise((r) => setTimeout(r, 350));
|
|
86
118
|
return yield factory();
|
|
87
119
|
}
|
|
88
120
|
catch (secondError) {
|
|
89
|
-
if ((0, is_stale_chunk_error_1.
|
|
90
|
-
|
|
121
|
+
if (!(0, is_stale_chunk_error_1.isRecoverableImportError)(secondError))
|
|
122
|
+
throw secondError;
|
|
123
|
+
if (reloadOnce() !== 'exhausted') {
|
|
124
|
+
// Страница уходит в reload — «подвешиваем» lazy навсегда,
|
|
125
|
+
// чтобы Suspense-лоадер висел до перезагрузки вместо
|
|
126
|
+
// мигания ErrorBoundary и лишнего события в Sentry.
|
|
127
|
+
return new Promise(() => { });
|
|
91
128
|
}
|
|
92
|
-
|
|
129
|
+
// Reload уже был, а чанк всё равно битый — это НЕ обычный
|
|
130
|
+
// апдейт, а сломанный деплой. Оборачиваем, чтобы событие
|
|
131
|
+
// не срезалось Sentry ignoreErrors и дошло до дашборда.
|
|
132
|
+
throw new ChunkRecoveryFailedError(secondError);
|
|
93
133
|
}
|
|
94
134
|
}
|
|
95
135
|
}));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@escapenavigator/hooks",
|
|
3
|
-
"version": "1.10.
|
|
3
|
+
"version": "1.10.154",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -14,9 +14,9 @@
|
|
|
14
14
|
"test": "jest"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@escapenavigator/services": "^1.10.
|
|
17
|
+
"@escapenavigator/services": "^1.10.162",
|
|
18
18
|
"@escapenavigator/types": "^1.10.150",
|
|
19
|
-
"@escapenavigator/utils": "^1.10.
|
|
19
|
+
"@escapenavigator/utils": "^1.10.157",
|
|
20
20
|
"libphonenumber-js": "^1.12.24",
|
|
21
21
|
"react": "19.2.6",
|
|
22
22
|
"react-phone-input-2": "^2.15.1"
|
|
@@ -32,5 +32,5 @@
|
|
|
32
32
|
"ts-jest": "^29.1.1",
|
|
33
33
|
"typescript": "^5.6"
|
|
34
34
|
},
|
|
35
|
-
"gitHead": "
|
|
35
|
+
"gitHead": "6ba35e576b925e6345c5f2114185de04e3d072e0"
|
|
36
36
|
}
|