@alfalab/bridge-to-native 1.3.2-beta.531b429 → 1.3.2-beta.6ebe433
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/client/bridge-to-native.d.ts +15 -20
- package/client/bridge-to-native.js +16 -19
- package/client/services-and-utils/native-navigation-and-title-service.d.ts +20 -17
- package/client/services-and-utils/native-navigation-and-title-service.js +137 -92
- package/client/types.d.ts +2 -0
- package/package.json +1 -1
- package/query-and-headers-keys.d.ts +1 -0
- package/query-and-headers-keys.js +4 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { type BrowserHistoryApiWrappers, type HistoryPushStateParams, type LocationAssignParam, type LogError, type NativeFeatureKey, type PdfType } from './types';
|
|
2
|
+
import { type BrowserHistoryApiWrappers, type HistoryPushStateParams, type HistoryReplaceStateParams, type LocationAssignParam, type LogError, type NativeFeatureKey, type PdfType } from './types';
|
|
3
3
|
/**
|
|
4
4
|
* Сервис, предоставляет методы для WA, работающего внутри NA.
|
|
5
5
|
*/
|
|
@@ -89,12 +89,6 @@ export declare class BridgeToNative {
|
|
|
89
89
|
* Делает несколько шагов назад по браузерной истории и модифицирует внутреннее состояние,
|
|
90
90
|
* чтобы в дальнейшем зарегистрировать этот переход в NA.
|
|
91
91
|
*
|
|
92
|
-
* ВАЖНО!
|
|
93
|
-
* Метод можно использовать только в рамках истории по SPA WA! Движение назад
|
|
94
|
-
* server-side переходом на несколько шагов не поддерживается.
|
|
95
|
-
*
|
|
96
|
-
* Снять это ограничение возможно, но нужны доработки.
|
|
97
|
-
*
|
|
98
92
|
* @param stepsNumber Количество шагов назад.
|
|
99
93
|
* Возможно передача как положительного, так и отрицательного числа. `0` будет проигнорирован.
|
|
100
94
|
* @param autoCloseWebview Флаг — закрывать ли WV автоматически,
|
|
@@ -138,6 +132,7 @@ export declare class BridgeToNative {
|
|
|
138
132
|
*
|
|
139
133
|
* @param url URL для перехода внутри WA client-side навигацией.
|
|
140
134
|
* @param state <https://developer.mozilla.org/en-US/docs/Web/API/History/state> для новой записи в истории.
|
|
135
|
+
* Должен быть объектом или `null`. Примитивы (строка, число и т.п.) будут потеряны.
|
|
141
136
|
* @param nativeTitle Текст заголовка, для «нативной» части WV, пустая строка — отсутствие заголовка.
|
|
142
137
|
*/
|
|
143
138
|
navigateClientSide(url: HistoryPushStateParams[2], state?: HistoryPushStateParams[0], nativeTitle?: string): void;
|
|
@@ -146,17 +141,6 @@ export declare class BridgeToNative {
|
|
|
146
141
|
* чтобы корректно передать информацию экземпляру B2N следующего WA или
|
|
147
142
|
* экзепляру B2N следующей страницы текущего WA (в случае multi-page application).
|
|
148
143
|
*
|
|
149
|
-
* ВАЖНО!
|
|
150
|
-
*
|
|
151
|
-
* Не поддерживаются такие сценарии:
|
|
152
|
-
*
|
|
153
|
-
* 1. Микс client-side навигации и server-side навигации в рамках одного WA.
|
|
154
|
-
* т.е. одно WA должно использовать либо только `navigateClientSide`, либо только `navigateServerSide`.
|
|
155
|
-
* 2. Старт в WA 1 → переход к WA 2 → переход к WA 1,
|
|
156
|
-
* т.е. при использовании server-side навигации, история переходов разных WA не должна смешиваться.
|
|
157
|
-
*
|
|
158
|
-
* Снять эти ограничения возможно, но нужны доработки.
|
|
159
|
-
*
|
|
160
144
|
* @param url URL для перехода внутри WA server-side навигацией.
|
|
161
145
|
* @param nativeTitle Текст заголовка, для «нативной» части WV, пустая строка — отсутствие заголовка.
|
|
162
146
|
*/
|
|
@@ -206,10 +190,21 @@ export declare class BridgeToNative {
|
|
|
206
190
|
/**
|
|
207
191
|
* Для перезагрузки страницы необходимо использовать этот метод.
|
|
208
192
|
* Иначе синхронизация состояния с NA будет потеряна.
|
|
209
|
-
*
|
|
210
|
-
*
|
|
193
|
+
*
|
|
194
|
+
* @param skipReload По умолчанию метод сам делает `location.reload`,
|
|
195
|
+
* но с помощью аргумента можно отключить этот вызов, если нужно.
|
|
211
196
|
*/
|
|
212
197
|
reload(skipReload?: boolean): void;
|
|
198
|
+
/**
|
|
199
|
+
* Позволяет изменить `history.state` и/или URL текущей записи без потери служебного свойства B2N.
|
|
200
|
+
* Используйте этот метод вместо прямого вызова `history.replaceState`.
|
|
201
|
+
* Прямой вызов `history.replaceState` приведёт к потере `b2n-pageId` и нарушит работу навигации.
|
|
202
|
+
*
|
|
203
|
+
* @param url URL для замены.
|
|
204
|
+
* @param state <https://developer.mozilla.org/en-US/docs/Web/API/History/state>.
|
|
205
|
+
* Должен быть объектом или `null`. Примитивы (строка, число и т.п.) будут потеряны.
|
|
206
|
+
*/
|
|
207
|
+
replaceHistoryState(url?: HistoryReplaceStateParams[2], state?: HistoryReplaceStateParams[0]): void;
|
|
213
208
|
/**
|
|
214
209
|
* Информирует NA, что WA находится на первом экране. Это приведёт к тому,
|
|
215
210
|
* что следующее нажатие на кнопку «Назад» в NA закроет WV.
|
|
@@ -115,12 +115,6 @@ class BridgeToNative {
|
|
|
115
115
|
* Делает несколько шагов назад по браузерной истории и модифицирует внутреннее состояние,
|
|
116
116
|
* чтобы в дальнейшем зарегистрировать этот переход в NA.
|
|
117
117
|
*
|
|
118
|
-
* ВАЖНО!
|
|
119
|
-
* Метод можно использовать только в рамках истории по SPA WA! Движение назад
|
|
120
|
-
* server-side переходом на несколько шагов не поддерживается.
|
|
121
|
-
*
|
|
122
|
-
* Снять это ограничение возможно, но нужны доработки.
|
|
123
|
-
*
|
|
124
118
|
* @param stepsNumber Количество шагов назад.
|
|
125
119
|
* Возможно передача как положительного, так и отрицательного числа. `0` будет проигнорирован.
|
|
126
120
|
* @param autoCloseWebview Флаг — закрывать ли WV автоматически,
|
|
@@ -170,6 +164,7 @@ class BridgeToNative {
|
|
|
170
164
|
*
|
|
171
165
|
* @param url URL для перехода внутри WA client-side навигацией.
|
|
172
166
|
* @param state <https://developer.mozilla.org/en-US/docs/Web/API/History/state> для новой записи в истории.
|
|
167
|
+
* Должен быть объектом или `null`. Примитивы (строка, число и т.п.) будут потеряны.
|
|
173
168
|
* @param nativeTitle Текст заголовка, для «нативной» части WV, пустая строка — отсутствие заголовка.
|
|
174
169
|
*/
|
|
175
170
|
navigateClientSide(url, state, nativeTitle = '') {
|
|
@@ -180,17 +175,6 @@ class BridgeToNative {
|
|
|
180
175
|
* чтобы корректно передать информацию экземпляру B2N следующего WA или
|
|
181
176
|
* экзепляру B2N следующей страницы текущего WA (в случае multi-page application).
|
|
182
177
|
*
|
|
183
|
-
* ВАЖНО!
|
|
184
|
-
*
|
|
185
|
-
* Не поддерживаются такие сценарии:
|
|
186
|
-
*
|
|
187
|
-
* 1. Микс client-side навигации и server-side навигации в рамках одного WA.
|
|
188
|
-
* т.е. одно WA должно использовать либо только `navigateClientSide`, либо только `navigateServerSide`.
|
|
189
|
-
* 2. Старт в WA 1 → переход к WA 2 → переход к WA 1,
|
|
190
|
-
* т.е. при использовании server-side навигации, история переходов разных WA не должна смешиваться.
|
|
191
|
-
*
|
|
192
|
-
* Снять эти ограничения возможно, но нужны доработки.
|
|
193
|
-
*
|
|
194
178
|
* @param url URL для перехода внутри WA server-side навигацией.
|
|
195
179
|
* @param nativeTitle Текст заголовка, для «нативной» части WV, пустая строка — отсутствие заголовка.
|
|
196
180
|
*/
|
|
@@ -248,12 +232,25 @@ class BridgeToNative {
|
|
|
248
232
|
/**
|
|
249
233
|
* Для перезагрузки страницы необходимо использовать этот метод.
|
|
250
234
|
* Иначе синхронизация состояния с NA будет потеряна.
|
|
251
|
-
*
|
|
252
|
-
*
|
|
235
|
+
*
|
|
236
|
+
* @param skipReload По умолчанию метод сам делает `location.reload`,
|
|
237
|
+
* но с помощью аргумента можно отключить этот вызов, если нужно.
|
|
253
238
|
*/
|
|
254
239
|
reload(skipReload) {
|
|
255
240
|
this.nativeNavigationAndTitleService.reload(skipReload);
|
|
256
241
|
}
|
|
242
|
+
/**
|
|
243
|
+
* Позволяет изменить `history.state` и/или URL текущей записи без потери служебного свойства B2N.
|
|
244
|
+
* Используйте этот метод вместо прямого вызова `history.replaceState`.
|
|
245
|
+
* Прямой вызов `history.replaceState` приведёт к потере `b2n-pageId` и нарушит работу навигации.
|
|
246
|
+
*
|
|
247
|
+
* @param url URL для замены.
|
|
248
|
+
* @param state <https://developer.mozilla.org/en-US/docs/Web/API/History/state>.
|
|
249
|
+
* Должен быть объектом или `null`. Примитивы (строка, число и т.п.) будут потеряны.
|
|
250
|
+
*/
|
|
251
|
+
replaceHistoryState(url, state = null) {
|
|
252
|
+
this.nativeNavigationAndTitleService.replaceHistoryState(url, state);
|
|
253
|
+
}
|
|
257
254
|
/**
|
|
258
255
|
* Информирует NA, что WA находится на первом экране. Это приведёт к тому,
|
|
259
256
|
* что следующее нажатие на кнопку «Назад» в NA закроет WV.
|
|
@@ -1,17 +1,20 @@
|
|
|
1
|
-
|
|
2
1
|
import { type BrowserHistoryApiWrappers, type HistoryPushStateParams, type LocationAssignParam, type LogError } from '../types';
|
|
3
2
|
import { type NativeParamsService } from './native-params-service';
|
|
4
3
|
/**
|
|
5
4
|
* Сервис, отвечающий за взаимодействие WA с WV компонентами NA —
|
|
6
5
|
* «заголовком» и кнопкой «назад».
|
|
6
|
+
*
|
|
7
|
+
* Подробное описание сценариев навигации и логики восстановления состояния при hard навигации
|
|
8
|
+
* см. в документе {@link ./NAVIGATION_SCENARIOS.md}.
|
|
7
9
|
*/
|
|
8
10
|
export declare class NativeNavigationAndTitleService {
|
|
9
11
|
private nativeParamsService;
|
|
10
12
|
private browserHistoryApiWrappers?;
|
|
11
13
|
private logError?;
|
|
12
14
|
private nativeHistoryStack;
|
|
13
|
-
private numOfBackSteps;
|
|
14
15
|
private lastSetPageSettingsParams;
|
|
16
|
+
private isGoBackLocked;
|
|
17
|
+
private isNavigateServerSideLocked;
|
|
15
18
|
constructor(nativeParamsService: NativeParamsService, browserHistoryApiWrappers?: BrowserHistoryApiWrappers | undefined, logError?: LogError | undefined);
|
|
16
19
|
closeWebview(): void;
|
|
17
20
|
goBack(): void;
|
|
@@ -21,6 +24,9 @@ export declare class NativeNavigationAndTitleService {
|
|
|
21
24
|
reload(skipReload?: boolean): void;
|
|
22
25
|
setInitialView(nativeTitle?: string): void;
|
|
23
26
|
setTitle(nativeTitle: string): void;
|
|
27
|
+
replaceHistoryState(url?: HistoryPushStateParams[2], state?: HistoryPushStateParams[0]): void;
|
|
28
|
+
private createStateWithPageId;
|
|
29
|
+
private setHistoryStatePageId;
|
|
24
30
|
/**
|
|
25
31
|
* Метод, вычисляющий `pageId`, который нужно послать в NA
|
|
26
32
|
* для правильной синхронизации с кнопкой "Назад". Также вычисляет `pageTitle`
|
|
@@ -34,14 +40,20 @@ export declare class NativeNavigationAndTitleService {
|
|
|
34
40
|
* после нажатия на кнопку «Назад» в NA, вызова `history.back()` и `history.go(-x)`.
|
|
35
41
|
*/
|
|
36
42
|
private handleClientSideNavigationBack;
|
|
37
|
-
private
|
|
43
|
+
private hasSavedHistoryStack;
|
|
38
44
|
/**
|
|
39
|
-
* Инициализирует `nativeHistoryStack
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* - Инициализация при server-side переходе «назад» по истории (Сценарий 3).
|
|
45
|
+
* Инициализирует `nativeHistoryStack`.
|
|
46
|
+
*
|
|
47
|
+
* Подробное описание каждого сценария см. в {@link ./NAVIGATION_SCENARIOS.md}.
|
|
43
48
|
*/
|
|
44
49
|
private initializeNativeHistoryStack;
|
|
50
|
+
private initializeForNewOrigin;
|
|
51
|
+
private initializeForForward;
|
|
52
|
+
/**
|
|
53
|
+
* Читает и парсит `nativeHistoryStack` из SessionStorage.
|
|
54
|
+
* При ошибке чтения или парсинга логирует через `logError` и пробрасывает исключение.
|
|
55
|
+
*/
|
|
56
|
+
private readSavedHistoryStack;
|
|
45
57
|
/**
|
|
46
58
|
* Подготавливает ссылку для корректного перехода server-side навигацией.
|
|
47
59
|
*
|
|
@@ -50,17 +62,8 @@ export declare class NativeNavigationAndTitleService {
|
|
|
50
62
|
* экзепляра B2N следующей страницы текущего WA
|
|
51
63
|
*/
|
|
52
64
|
private prepareExternalLinkBeforeOpen;
|
|
53
|
-
private static shouldInitializeFromNextPageId;
|
|
54
|
-
/**
|
|
55
|
-
* Читает сохраннённый в sessionStorage `nativeHistoryStack`,
|
|
56
|
-
* снова сохраняет его в sessionStorage, уменьшая список на одну запись,
|
|
57
|
-
* на случай, если будет дальнейший переход назад server-side навигацией.
|
|
58
|
-
*
|
|
59
|
-
* @returns Актуальное состояние `nativeHistoryStack` из sessionStorage.
|
|
60
|
-
*/
|
|
61
|
-
private readAndUpdateNativeHistoryStackSessionStorage;
|
|
62
65
|
/**
|
|
63
|
-
*
|
|
66
|
+
* Сохраняет `nativeHistoryStack` в SessionStorage.
|
|
64
67
|
*/
|
|
65
68
|
private saveNativeHistoryStack;
|
|
66
69
|
/**
|
|
@@ -1,23 +1,33 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/* eslint max-lines: ["error", {"skipComments": true}] */
|
|
2
|
+
/* eslint max-lines: ["error", {"max": 400, "skipComments": true}] */
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
exports.NativeNavigationAndTitleService = void 0;
|
|
5
5
|
const query_and_headers_keys_1 = require("../../query-and-headers-keys");
|
|
6
6
|
const close_webview_util_1 = require("./close-webview-util");
|
|
7
|
+
const NativeHistoryStackStub = 0;
|
|
7
8
|
/**
|
|
8
9
|
* Сервис, отвечающий за взаимодействие WA с WV компонентами NA —
|
|
9
10
|
* «заголовком» и кнопкой «назад».
|
|
11
|
+
*
|
|
12
|
+
* Подробное описание сценариев навигации и логики восстановления состояния при hard навигации
|
|
13
|
+
* см. в документе {@link ./NAVIGATION_SCENARIOS.md}.
|
|
10
14
|
*/
|
|
11
15
|
class NativeNavigationAndTitleService {
|
|
12
16
|
constructor(nativeParamsService, browserHistoryApiWrappers, logError) {
|
|
13
17
|
this.nativeParamsService = nativeParamsService;
|
|
14
18
|
this.browserHistoryApiWrappers = browserHistoryApiWrappers;
|
|
15
19
|
this.logError = logError;
|
|
16
|
-
// Поле, помогающее правильно обработать переход «назад» на несколько шагов.
|
|
17
|
-
this.numOfBackSteps = 1;
|
|
18
20
|
// Здесь сохраняются параметры, которые в последний раз были отправлены
|
|
19
21
|
// в NA. Помогает предотвратить повторную отправку одинаковых параметров.
|
|
20
22
|
this.lastSetPageSettingsParams = '';
|
|
23
|
+
// Предотвращают повторный вызов навигации, пока текущая не завершена.
|
|
24
|
+
// Без блокировки WV-браузер продолжит показывать исходную страницу,
|
|
25
|
+
// и повторный вызов может инициировать нежелательную навигацию.
|
|
26
|
+
// `isGoBackLocked` снимается в `handleClientSideNavigationBack` (popstate) для soft-навигации;
|
|
27
|
+
// при hard-навигации блокировка снимется автоматически при новой инициализации.
|
|
28
|
+
// `isNavigateServerSideLocked` не снимается — после server-side навигации всегда новая инициализация.
|
|
29
|
+
this.isGoBackLocked = false;
|
|
30
|
+
this.isNavigateServerSideLocked = false;
|
|
21
31
|
this.handleClientSideNavigationBack = this.handleClientSideNavigationBack.bind(this);
|
|
22
32
|
window.addEventListener('popstate', this.handleClientSideNavigationBack); // без отписки т.к. Сервис используется в течение всей жизни WA
|
|
23
33
|
this.initializeNativeHistoryStack();
|
|
@@ -27,6 +37,10 @@ class NativeNavigationAndTitleService {
|
|
|
27
37
|
(0, close_webview_util_1.closeWebviewUtil)();
|
|
28
38
|
}
|
|
29
39
|
goBack() {
|
|
40
|
+
if (this.isGoBackLocked) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
this.isGoBackLocked = true;
|
|
30
44
|
this.goBackAFewStepsClientSide(-1, true);
|
|
31
45
|
}
|
|
32
46
|
goBackAFewStepsClientSide(stepsNumber, autoCloseWebview = false) {
|
|
@@ -41,12 +55,8 @@ class NativeNavigationAndTitleService {
|
|
|
41
55
|
(0, close_webview_util_1.closeWebviewUtil)();
|
|
42
56
|
return;
|
|
43
57
|
}
|
|
44
|
-
this.numOfBackSteps = maxStepsToBack;
|
|
45
58
|
}
|
|
46
|
-
|
|
47
|
-
this.numOfBackSteps = stepsToBack;
|
|
48
|
-
}
|
|
49
|
-
const steps = -this.numOfBackSteps;
|
|
59
|
+
const steps = -Math.min(stepsToBack, maxStepsToBack);
|
|
50
60
|
if ((_a = this.browserHistoryApiWrappers) === null || _a === void 0 ? void 0 : _a.go) {
|
|
51
61
|
this.browserHistoryApiWrappers.go(steps);
|
|
52
62
|
}
|
|
@@ -56,30 +66,33 @@ class NativeNavigationAndTitleService {
|
|
|
56
66
|
// Далее сработает подписка на `popstate`, см. метод `handleClientSideNavigationBack`.
|
|
57
67
|
}
|
|
58
68
|
navigateClientSide(url, state = null, nativeTitle = '') {
|
|
59
|
-
var _a
|
|
69
|
+
var _a;
|
|
60
70
|
if ((_a = this.browserHistoryApiWrappers) === null || _a === void 0 ? void 0 : _a.push) {
|
|
61
|
-
|
|
71
|
+
this.browserHistoryApiWrappers.push(url, state);
|
|
62
72
|
}
|
|
63
73
|
else {
|
|
64
74
|
window.history.pushState(state, '', url);
|
|
65
75
|
}
|
|
76
|
+
this.setHistoryStatePageId(true);
|
|
66
77
|
this.nativeHistoryStack.push(nativeTitle);
|
|
78
|
+
this.saveNativeHistoryStack();
|
|
67
79
|
this.syncHistoryWithNative();
|
|
68
80
|
}
|
|
69
81
|
navigateServerSide(link, nativeTitle = '') {
|
|
82
|
+
if (this.isNavigateServerSideLocked) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
this.isNavigateServerSideLocked = true;
|
|
70
86
|
const url = link instanceof URL ? link : new URL(link);
|
|
87
|
+
this.nativeHistoryStack.push(nativeTitle || '');
|
|
71
88
|
if (nativeTitle) {
|
|
72
89
|
url.searchParams.set(query_and_headers_keys_1.QUERY_B2N_TITLE, nativeTitle);
|
|
73
90
|
}
|
|
74
|
-
// TODO: Предыдущая реализация на iOS открывала новое WV. Возможно, что-то плохо работало,
|
|
75
|
-
// обязательно протестировать.
|
|
76
91
|
this.saveNativeHistoryStack();
|
|
77
92
|
window.location.assign(this.prepareExternalLinkBeforeOpen(url));
|
|
78
93
|
}
|
|
94
|
+
// eslint-disable-next-line class-methods-use-this -- удобней использовать метод в контексте экземпляра.
|
|
79
95
|
reload(skipReload = false) {
|
|
80
|
-
this.nativeHistoryStack.push(1 /* NativeHistoryStackSpecialValues.TemporaryReloadStub */); // небольшой костыль, чтобы переиспользовать server-side сценарий
|
|
81
|
-
this.saveNativeHistoryStack();
|
|
82
|
-
// информация для серверной стороны B2N, что происходит `reload` и парсить запрос на предмет NA параметров не нужно (в нем их скорее всего не будет)
|
|
83
96
|
document.cookie = `${query_and_headers_keys_1.COOKIE_KEY_BRIDGE_TO_NATIVE_RELOAD}=true; Path=/`;
|
|
84
97
|
if (!skipReload) {
|
|
85
98
|
window.location.reload();
|
|
@@ -87,12 +100,39 @@ class NativeNavigationAndTitleService {
|
|
|
87
100
|
}
|
|
88
101
|
setInitialView(nativeTitle = '') {
|
|
89
102
|
this.nativeHistoryStack = [nativeTitle];
|
|
103
|
+
this.saveNativeHistoryStack();
|
|
90
104
|
this.syncHistoryWithNative();
|
|
91
105
|
}
|
|
92
106
|
setTitle(nativeTitle) {
|
|
93
107
|
this.nativeHistoryStack[this.nativeHistoryStack.length - 1] = nativeTitle;
|
|
108
|
+
this.saveNativeHistoryStack();
|
|
94
109
|
this.syncHistoryWithNative();
|
|
95
110
|
}
|
|
111
|
+
replaceHistoryState(url, state = null) {
|
|
112
|
+
var _a;
|
|
113
|
+
if ((_a = this.browserHistoryApiWrappers) === null || _a === void 0 ? void 0 : _a.replace) {
|
|
114
|
+
this.browserHistoryApiWrappers.replace(url, state);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
window.history.replaceState(state, '', url);
|
|
118
|
+
}
|
|
119
|
+
this.setHistoryStatePageId();
|
|
120
|
+
}
|
|
121
|
+
// eslint-disable-next-line class-methods-use-this -- удобней использовать метод в контексте экземпляра.
|
|
122
|
+
createStateWithPageId(state, pageId) {
|
|
123
|
+
const isPlainObject = (v) => v !== null && v !== undefined && typeof v === 'object' && !Array.isArray(v);
|
|
124
|
+
return Object.assign(Object.assign({}, (isPlainObject(state) ? state : {})), { [query_and_headers_keys_1.HISTORY_STATE_KEY_B2N_PAGE_ID]: pageId });
|
|
125
|
+
}
|
|
126
|
+
setHistoryStatePageId(useNextPageId = false) {
|
|
127
|
+
const pageId = useNextPageId
|
|
128
|
+
? this.nativeHistoryStack.length + 1
|
|
129
|
+
: this.nativeHistoryStack.length;
|
|
130
|
+
const newState = this.createStateWithPageId(window.history.state, pageId);
|
|
131
|
+
// `b2n-pageId` всегда записывается на верхний уровень через нативный `replaceState`,
|
|
132
|
+
// а не через wrapper, чтобы wrapper (например, React Router / history) не мог обернуть
|
|
133
|
+
// его внутрь своего формата и скрыть от B2N при чтении `history.state`.
|
|
134
|
+
window.history.replaceState(newState, '');
|
|
135
|
+
}
|
|
96
136
|
/**
|
|
97
137
|
* Метод, вычисляющий `pageId`, который нужно послать в NA
|
|
98
138
|
* для правильной синхронизации с кнопкой "Назад". Также вычисляет `pageTitle`
|
|
@@ -115,52 +155,104 @@ class NativeNavigationAndTitleService {
|
|
|
115
155
|
* Обработчик для `window.onpopstate` события. Который сработает
|
|
116
156
|
* после нажатия на кнопку «Назад» в NA, вызова `history.back()` и `history.go(-x)`.
|
|
117
157
|
*/
|
|
118
|
-
handleClientSideNavigationBack() {
|
|
119
|
-
|
|
120
|
-
this.
|
|
158
|
+
handleClientSideNavigationBack(event) {
|
|
159
|
+
var _a;
|
|
160
|
+
this.isGoBackLocked = false;
|
|
161
|
+
const statePageId = (_a = event === null || event === void 0 ? void 0 : event.state) === null || _a === void 0 ? void 0 : _a[query_and_headers_keys_1.HISTORY_STATE_KEY_B2N_PAGE_ID];
|
|
162
|
+
if (typeof statePageId === 'number') {
|
|
163
|
+
this.nativeHistoryStack = this.nativeHistoryStack.slice(0, statePageId);
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
this.nativeHistoryStack = this.nativeHistoryStack.slice(0, -1);
|
|
167
|
+
}
|
|
121
168
|
if (this.nativeHistoryStack.length < 1) {
|
|
122
169
|
(0, close_webview_util_1.closeWebviewUtil)();
|
|
123
170
|
return;
|
|
124
171
|
}
|
|
172
|
+
this.saveNativeHistoryStack();
|
|
125
173
|
this.syncHistoryWithNative();
|
|
126
174
|
}
|
|
127
|
-
|
|
175
|
+
// eslint-disable-next-line class-methods-use-this -- удобней использовать метод в контексте экземпляра.
|
|
176
|
+
hasSavedHistoryStack() {
|
|
128
177
|
return sessionStorage.getItem(query_and_headers_keys_1.SS_KEY_BRIDGE_TO_NATIVE_HISTORY_STACK) !== null;
|
|
129
178
|
}
|
|
130
179
|
/**
|
|
131
|
-
* Инициализирует `nativeHistoryStack
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
* - Инициализация при server-side переходе «назад» по истории (Сценарий 3).
|
|
180
|
+
* Инициализирует `nativeHistoryStack`.
|
|
181
|
+
*
|
|
182
|
+
* Подробное описание каждого сценария см. в {@link ./NAVIGATION_SCENARIOS.md}.
|
|
135
183
|
*/
|
|
136
184
|
initializeNativeHistoryStack() {
|
|
185
|
+
var _a;
|
|
137
186
|
const { nextPageId, title } = this.nativeParamsService;
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
try {
|
|
152
|
-
this.nativeHistoryStack = this.readAndUpdateNativeHistoryStackSessionStorage();
|
|
187
|
+
const hasSS = this.hasSavedHistoryStack();
|
|
188
|
+
const statePageId = (_a = window.history.state) === null || _a === void 0 ? void 0 : _a[query_and_headers_keys_1.HISTORY_STATE_KEY_B2N_PAGE_ID];
|
|
189
|
+
try {
|
|
190
|
+
if (typeof statePageId === 'number') {
|
|
191
|
+
const pageId = statePageId;
|
|
192
|
+
if (hasSS) {
|
|
193
|
+
const savedStack = this.readSavedHistoryStack();
|
|
194
|
+
this.nativeHistoryStack = savedStack.slice(0, pageId);
|
|
195
|
+
this.nativeHistoryStack[this.nativeHistoryStack.length - 1] = title;
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
this.nativeHistoryStack = [title];
|
|
199
|
+
}
|
|
153
200
|
}
|
|
154
|
-
|
|
155
|
-
this.nativeHistoryStack =
|
|
201
|
+
else if (nextPageId && !hasSS) {
|
|
202
|
+
this.nativeHistoryStack = this.initializeForNewOrigin(nextPageId, title);
|
|
203
|
+
}
|
|
204
|
+
else if (nextPageId && hasSS) {
|
|
205
|
+
this.nativeHistoryStack = this.initializeForForward(nextPageId, title);
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
this.nativeHistoryStack = [title];
|
|
156
209
|
}
|
|
157
210
|
}
|
|
158
|
-
|
|
159
|
-
// Сценарий 1 - запись в sessionStorage ставит метод `this.navigateServerSide`,
|
|
160
|
-
// её нет, значит это инициализация сразу после открытия нового WV.
|
|
211
|
+
catch (_b) {
|
|
161
212
|
this.nativeHistoryStack = [title];
|
|
162
213
|
}
|
|
214
|
+
this.saveNativeHistoryStack();
|
|
163
215
|
this.syncHistoryWithNative();
|
|
216
|
+
this.setHistoryStatePageId();
|
|
217
|
+
}
|
|
218
|
+
// eslint-disable-next-line class-methods-use-this -- удобней использовать метод в контексте экземпляра.
|
|
219
|
+
initializeForNewOrigin(nextPageId, title) {
|
|
220
|
+
const stack = new Array(nextPageId).fill(NativeHistoryStackStub);
|
|
221
|
+
stack[stack.length - 1] = title;
|
|
222
|
+
return stack;
|
|
223
|
+
}
|
|
224
|
+
initializeForForward(nextPageId, title) {
|
|
225
|
+
const savedStack = this.readSavedHistoryStack();
|
|
226
|
+
if (savedStack.length === nextPageId) {
|
|
227
|
+
savedStack[savedStack.length - 1] = title;
|
|
228
|
+
return savedStack;
|
|
229
|
+
}
|
|
230
|
+
const stack = new Array(nextPageId).fill(NativeHistoryStackStub);
|
|
231
|
+
for (let i = 0; i < savedStack.length && i < nextPageId; i++) {
|
|
232
|
+
stack[i] = savedStack[i];
|
|
233
|
+
}
|
|
234
|
+
stack[stack.length - 1] = title;
|
|
235
|
+
return stack;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Читает и парсит `nativeHistoryStack` из SessionStorage.
|
|
239
|
+
* При ошибке чтения или парсинга логирует через `logError` и пробрасывает исключение.
|
|
240
|
+
*/
|
|
241
|
+
readSavedHistoryStack() {
|
|
242
|
+
const serialized = sessionStorage.getItem(query_and_headers_keys_1.SS_KEY_BRIDGE_TO_NATIVE_HISTORY_STACK);
|
|
243
|
+
try {
|
|
244
|
+
if (!serialized) {
|
|
245
|
+
throw new Error(`${query_and_headers_keys_1.SS_KEY_BRIDGE_TO_NATIVE_HISTORY_STACK} sessionStorage expected not to be null`);
|
|
246
|
+
}
|
|
247
|
+
return JSON.parse(serialized);
|
|
248
|
+
}
|
|
249
|
+
catch (e) {
|
|
250
|
+
if (this.logError) {
|
|
251
|
+
this.logError(`Клиентский код B2N не смог получить ${query_and_headers_keys_1.SS_KEY_BRIDGE_TO_NATIVE_HISTORY_STACK} из sessionStorage
|
|
252
|
+
Могут возникнуть проблемы с кнопкой «Назад» в NA.`, e);
|
|
253
|
+
}
|
|
254
|
+
throw e;
|
|
255
|
+
}
|
|
164
256
|
}
|
|
165
257
|
/**
|
|
166
258
|
* Подготавливает ссылку для корректного перехода server-side навигацией.
|
|
@@ -183,58 +275,11 @@ class NativeNavigationAndTitleService {
|
|
|
183
275
|
// Таким образом гарантируется, что версию приложения будет видеть следующее WA
|
|
184
276
|
// (заголовок `app-version` может отсутствовать при server-side переходах).
|
|
185
277
|
modifiedUrl.searchParams.set(query_and_headers_keys_1.QUERY_NATIVE_IOS_APPVERSION, appVersion);
|
|
186
|
-
modifiedUrl.searchParams.set(query_and_headers_keys_1.QUERY_B2N_NEXT_PAGEID,
|
|
278
|
+
modifiedUrl.searchParams.set(query_and_headers_keys_1.QUERY_B2N_NEXT_PAGEID, currentPageId.toString());
|
|
187
279
|
return modifiedUrl;
|
|
188
280
|
}
|
|
189
|
-
static shouldInitializeFromNextPageId(nextPageId) {
|
|
190
|
-
if (!NativeNavigationAndTitleService.hasSavedHistoryStack()) {
|
|
191
|
-
return true;
|
|
192
|
-
}
|
|
193
|
-
try {
|
|
194
|
-
const serializedNativeHistoryStack = sessionStorage.getItem(query_and_headers_keys_1.SS_KEY_BRIDGE_TO_NATIVE_HISTORY_STACK);
|
|
195
|
-
if (!serializedNativeHistoryStack) {
|
|
196
|
-
return true;
|
|
197
|
-
}
|
|
198
|
-
return (nextPageId ===
|
|
199
|
-
JSON.parse(serializedNativeHistoryStack).length +
|
|
200
|
-
1);
|
|
201
|
-
}
|
|
202
|
-
catch (_a) {
|
|
203
|
-
return true;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
/**
|
|
207
|
-
* Читает сохраннённый в sessionStorage `nativeHistoryStack`,
|
|
208
|
-
* снова сохраняет его в sessionStorage, уменьшая список на одну запись,
|
|
209
|
-
* на случай, если будет дальнейший переход назад server-side навигацией.
|
|
210
|
-
*
|
|
211
|
-
* @returns Актуальное состояние `nativeHistoryStack` из sessionStorage.
|
|
212
|
-
*/
|
|
213
|
-
readAndUpdateNativeHistoryStackSessionStorage() {
|
|
214
|
-
try {
|
|
215
|
-
const serializedNativeHistoryStack = sessionStorage.getItem(query_and_headers_keys_1.SS_KEY_BRIDGE_TO_NATIVE_HISTORY_STACK);
|
|
216
|
-
if (!serializedNativeHistoryStack) {
|
|
217
|
-
throw new Error();
|
|
218
|
-
}
|
|
219
|
-
const nativeHistoryStack = JSON.parse(serializedNativeHistoryStack); // происходит внутри оператора `catch`, поэтому кастинг типа приемлем
|
|
220
|
-
const nativeHistoryStackToSerialize = nativeHistoryStack.slice(0, -1);
|
|
221
|
-
sessionStorage.setItem(query_and_headers_keys_1.SS_KEY_BRIDGE_TO_NATIVE_HISTORY_STACK, JSON.stringify(nativeHistoryStackToSerialize));
|
|
222
|
-
if (nativeHistoryStack[nativeHistoryStack.length - 1] ===
|
|
223
|
-
1 /* NativeHistoryStackSpecialValues.TemporaryReloadStub */) {
|
|
224
|
-
return nativeHistoryStack.slice(0, -1);
|
|
225
|
-
}
|
|
226
|
-
return nativeHistoryStack;
|
|
227
|
-
}
|
|
228
|
-
catch (e) {
|
|
229
|
-
if (this.logError) {
|
|
230
|
-
this.logError('Клиентский код B2N не смог восстановить `nativeHistoryStack` из sessionStorage. ' +
|
|
231
|
-
'Могут возникнуть проблемы с кнопкой «Назад» в NA.', e);
|
|
232
|
-
}
|
|
233
|
-
throw new Error();
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
281
|
/**
|
|
237
|
-
*
|
|
282
|
+
* Сохраняет `nativeHistoryStack` в SessionStorage.
|
|
238
283
|
*/
|
|
239
284
|
saveNativeHistoryStack() {
|
|
240
285
|
const serializedNativeHistoryStack = JSON.stringify(this.nativeHistoryStack);
|
package/client/types.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
export type BrowserHistoryApiWrappers = {
|
|
2
2
|
push?: (url: HistoryPushStateParams[2], state: HistoryPushStateParams[0]) => void;
|
|
3
3
|
go?: (delta: number) => void;
|
|
4
|
+
replace?: (url: HistoryReplaceStateParams[2], state: HistoryReplaceStateParams[0]) => void;
|
|
4
5
|
};
|
|
5
6
|
export type Environment = 'android' | 'ios';
|
|
6
7
|
export type HistoryPushStateParams = Parameters<typeof window.history.pushState>;
|
|
8
|
+
export type HistoryReplaceStateParams = Parameters<typeof window.history.replaceState>;
|
|
7
9
|
export type LocationAssignParam = Parameters<typeof window.location.assign>[0];
|
|
8
10
|
export type LogError = (b2nErrorMessage: string, originalError: unknown) => void;
|
|
9
11
|
export type NativeFeatureKey = 'geolocation' | 'linksInBrowser' | 'savedBackStack';
|
package/package.json
CHANGED
|
@@ -4,6 +4,7 @@ export declare const COOKIE_KEY_BRIDGE_TO_NATIVE_DATA = "bridgeToNativeData";
|
|
|
4
4
|
export declare const COOKIE_KEY_BRIDGE_TO_NATIVE_RELOAD = "bridgeToNativeReload";
|
|
5
5
|
export declare const HEADER_KEY_NATIVE_APPVERSION = "app-version";
|
|
6
6
|
export declare const HEADER_KEY_WV_LAUNCH_TIME = "webview-launch-time";
|
|
7
|
+
export declare const HISTORY_STATE_KEY_B2N_PAGE_ID = "b2n-pageId";
|
|
7
8
|
export declare const QUERY_B2N_NEXT_PAGEID = "b2n-next-page-id";
|
|
8
9
|
export declare const QUERY_B2N_TITLE = "b2n-title";
|
|
9
10
|
export declare const QUERY_B2N_TITLE_DEPRECATED = "title";
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
* Ключи стандартных заголовков.
|
|
4
4
|
*/
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.SS_KEY_BRIDGE_TO_NATIVE_HISTORY_STACK = exports.QUERY_NATIVE_THEME = exports.QUERY_NATIVE_IOS_APPVERSION = exports.QUERY_NATIVE_IOS_APPID = exports.QUERY_B2N_TITLE_DEPRECATED = exports.QUERY_B2N_TITLE = exports.QUERY_B2N_NEXT_PAGEID = exports.HEADER_KEY_WV_LAUNCH_TIME = exports.HEADER_KEY_NATIVE_APPVERSION = exports.COOKIE_KEY_BRIDGE_TO_NATIVE_RELOAD = exports.COOKIE_KEY_BRIDGE_TO_NATIVE_DATA = exports.HEADER_KEY_USER_AGENT = exports.HEADER_KEY_COOKIE = void 0;
|
|
6
|
+
exports.SS_KEY_BRIDGE_TO_NATIVE_HISTORY_STACK = exports.QUERY_NATIVE_THEME = exports.QUERY_NATIVE_IOS_APPVERSION = exports.QUERY_NATIVE_IOS_APPID = exports.QUERY_B2N_TITLE_DEPRECATED = exports.QUERY_B2N_TITLE = exports.QUERY_B2N_NEXT_PAGEID = exports.HISTORY_STATE_KEY_B2N_PAGE_ID = exports.HEADER_KEY_WV_LAUNCH_TIME = exports.HEADER_KEY_NATIVE_APPVERSION = exports.COOKIE_KEY_BRIDGE_TO_NATIVE_RELOAD = exports.COOKIE_KEY_BRIDGE_TO_NATIVE_DATA = exports.HEADER_KEY_USER_AGENT = exports.HEADER_KEY_COOKIE = void 0;
|
|
7
7
|
exports.HEADER_KEY_COOKIE = 'cookie';
|
|
8
8
|
exports.HEADER_KEY_USER_AGENT = 'user-agent';
|
|
9
9
|
/*
|
|
10
|
-
* Ключи
|
|
10
|
+
* Ключи заголовков, query-параметров и пр., которые использует B2N.
|
|
11
11
|
*/
|
|
12
12
|
// Ключ cookie, с помощью которого серверная часть B2N передаст на клиент информацию об NA.
|
|
13
13
|
exports.COOKIE_KEY_BRIDGE_TO_NATIVE_DATA = 'bridgeToNativeData';
|
|
@@ -23,6 +23,8 @@ exports.COOKIE_KEY_BRIDGE_TO_NATIVE_RELOAD = 'bridgeToNativeReload';
|
|
|
23
23
|
exports.HEADER_KEY_NATIVE_APPVERSION = 'app-version';
|
|
24
24
|
// Ключ заголовка, в котором передается время старта открытия WV экрана в формате timestamp
|
|
25
25
|
exports.HEADER_KEY_WV_LAUNCH_TIME = 'webview-launch-time';
|
|
26
|
+
// Ключ в history.state, хранящий текущий pageId для корректной back-навигации.
|
|
27
|
+
exports.HISTORY_STATE_KEY_B2N_PAGE_ID = 'b2n-pageId';
|
|
26
28
|
// Флаг, предписывающий клиентскому коду B2N начать связь
|
|
27
29
|
// с нативным приложением с указанного в этом параметре id, а не с 1.
|
|
28
30
|
// Необходимо для server-side навигации.
|