@escapenavigator/hooks 1.10.150 → 1.10.152

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/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from './functional-popover';
2
2
  export * from './input-tel';
3
+ export * from './lazy-with-retry';
3
4
  export * from './players-and-variation-picker';
4
5
  export * from './use-debounced-value';
5
6
  export * from './use-localstore-state';
package/dist/index.js CHANGED
@@ -16,6 +16,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./functional-popover"), exports);
18
18
  __exportStar(require("./input-tel"), exports);
19
+ __exportStar(require("./lazy-with-retry"), exports);
19
20
  __exportStar(require("./players-and-variation-picker"), exports);
20
21
  __exportStar(require("./use-debounced-value"), exports);
21
22
  __exportStar(require("./use-localstore-state"), exports);
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Обёртка над `React.lazy`, устойчивая к «исчезнувшим» чанкам после деплоя.
3
+ * Общая для всех фронтов (app / orders), завязана на React → живёт в hooks.
4
+ *
5
+ * Кейс: у пользователя открыта старая вкладка с index-OLD.js. После релиза
6
+ * этого чанка на CDN уже нет → `import()` бросает (формулировки зависят от
7
+ * браузера — см. `isStaleChunkError` в @escapenavigator/utils):
8
+ * - "Failed to fetch dynamically imported module" (Chrome/Safari)
9
+ * - "error loading dynamically imported module" (Firefox)
10
+ * - "Importing a module script failed"
11
+ * - "ChunkLoadError" / "Loading chunk N failed"
12
+ *
13
+ * Алгоритм:
14
+ * 1. Один retry с небольшой паузой — на случай флапа сети/прогрева CDN.
15
+ * 2. Если опять упало и похоже на «протухший чанк» — один раз перезагружаем
16
+ * страницу с `?v=<ts>`, чтобы выкинуть HTTP-кэш. Флаг в sessionStorage
17
+ * защищает от циклической перезагрузки.
18
+ */
19
+ import { ComponentType, lazy } from 'react';
20
+ /** Query-параметр cache-bust после reload; не должен попадать в `where` таблиц. */
21
+ export declare const CHUNK_RELOAD_QUERY_PARAM = "v";
22
+ /**
23
+ * Убирает служебный `?v=` из адресной строки (replaceState, без reload).
24
+ * Вызываем до первого рендера и после успешной загрузки lazy-чанка.
25
+ */
26
+ export declare const stripChunkReloadQueryParam: () => boolean;
27
+ export declare function lazyWithRetry<T extends ComponentType<any>>(factory: () => Promise<{
28
+ default: T;
29
+ }>): ReturnType<typeof lazy<T>>;
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ /**
3
+ * Обёртка над `React.lazy`, устойчивая к «исчезнувшим» чанкам после деплоя.
4
+ * Общая для всех фронтов (app / orders), завязана на React → живёт в hooks.
5
+ *
6
+ * Кейс: у пользователя открыта старая вкладка с index-OLD.js. После релиза
7
+ * этого чанка на CDN уже нет → `import()` бросает (формулировки зависят от
8
+ * браузера — см. `isStaleChunkError` в @escapenavigator/utils):
9
+ * - "Failed to fetch dynamically imported module" (Chrome/Safari)
10
+ * - "error loading dynamically imported module" (Firefox)
11
+ * - "Importing a module script failed"
12
+ * - "ChunkLoadError" / "Loading chunk N failed"
13
+ *
14
+ * Алгоритм:
15
+ * 1. Один retry с небольшой паузой — на случай флапа сети/прогрева CDN.
16
+ * 2. Если опять упало и похоже на «протухший чанк» — один раз перезагружаем
17
+ * страницу с `?v=<ts>`, чтобы выкинуть HTTP-кэш. Флаг в sessionStorage
18
+ * защищает от циклической перезагрузки.
19
+ */
20
+ /* eslint-disable @typescript-eslint/no-explicit-any */
21
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
22
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
23
+ return new (P || (P = Promise))(function (resolve, reject) {
24
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
25
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
26
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
27
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
28
+ });
29
+ };
30
+ Object.defineProperty(exports, "__esModule", { value: true });
31
+ exports.stripChunkReloadQueryParam = exports.CHUNK_RELOAD_QUERY_PARAM = void 0;
32
+ exports.lazyWithRetry = lazyWithRetry;
33
+ const is_stale_chunk_error_1 = require("@escapenavigator/utils/dist/is-stale-chunk-error");
34
+ const react_1 = require("react");
35
+ const RELOAD_FLAG = 'lazy_chunk_reload_attempted';
36
+ /** Query-параметр cache-bust после reload; не должен попадать в `where` таблиц. */
37
+ exports.CHUNK_RELOAD_QUERY_PARAM = 'v';
38
+ /**
39
+ * Убирает служебный `?v=` из адресной строки (replaceState, без reload).
40
+ * Вызываем до первого рендера и после успешной загрузки lazy-чанка.
41
+ */
42
+ const stripChunkReloadQueryParam = () => {
43
+ if (typeof window === 'undefined')
44
+ return false;
45
+ const url = new URL(window.location.href);
46
+ if (!url.searchParams.has(exports.CHUNK_RELOAD_QUERY_PARAM))
47
+ return false;
48
+ url.searchParams.delete(exports.CHUNK_RELOAD_QUERY_PARAM);
49
+ const next = url.pathname + (url.searchParams.toString() ? `?${url.searchParams}` : '') + url.hash;
50
+ window.history.replaceState(window.history.state, '', next);
51
+ return true;
52
+ };
53
+ exports.stripChunkReloadQueryParam = stripChunkReloadQueryParam;
54
+ const reloadOnce = () => {
55
+ try {
56
+ if (sessionStorage.getItem(RELOAD_FLAG))
57
+ return;
58
+ sessionStorage.setItem(RELOAD_FLAG, '1');
59
+ }
60
+ catch (_a) {
61
+ // sessionStorage может быть недоступен (Safari privacy / iframe);
62
+ // тогда просто перезагрузимся, лучше один лишний reload, чем белый экран.
63
+ }
64
+ const url = new URL(window.location.href);
65
+ url.searchParams.set(exports.CHUNK_RELOAD_QUERY_PARAM, Date.now().toString(36));
66
+ window.location.replace(url.toString());
67
+ };
68
+ function lazyWithRetry(factory) {
69
+ return (0, react_1.lazy)(() => __awaiter(this, void 0, void 0, function* () {
70
+ try {
71
+ const mod = yield factory();
72
+ try {
73
+ sessionStorage.removeItem(RELOAD_FLAG);
74
+ }
75
+ catch (_a) {
76
+ // см. reloadOnce: storage может быть недоступен
77
+ }
78
+ (0, exports.stripChunkReloadQueryParam)();
79
+ return mod;
80
+ }
81
+ catch (firstError) {
82
+ if (!(0, is_stale_chunk_error_1.isStaleChunkError)(firstError))
83
+ throw firstError;
84
+ try {
85
+ yield new Promise((r) => setTimeout(r, 350));
86
+ return yield factory();
87
+ }
88
+ catch (secondError) {
89
+ if ((0, is_stale_chunk_error_1.isStaleChunkError)(secondError)) {
90
+ reloadOnce();
91
+ }
92
+ throw secondError;
93
+ }
94
+ }
95
+ }));
96
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@escapenavigator/hooks",
3
- "version": "1.10.150",
3
+ "version": "1.10.152",
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.158",
18
- "@escapenavigator/types": "^1.10.148",
19
- "@escapenavigator/utils": "^1.10.153",
17
+ "@escapenavigator/services": "^1.10.160",
18
+ "@escapenavigator/types": "^1.10.149",
19
+ "@escapenavigator/utils": "^1.10.155",
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": "141d0c0d4b5a5bbf13f481696d604cb4953a8bdb"
35
+ "gitHead": "e868f8962dfa6caab506d64bd4c6c091514e44a2"
36
36
  }