@alfalab/bridge-to-native 1.3.2 → 1.4.0-beta.560edfc

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,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
  */
@@ -90,16 +90,27 @@ export declare class BridgeToNative {
90
90
  * чтобы в дальнейшем зарегистрировать этот переход в NA.
91
91
  *
92
92
  * ВАЖНО!
93
- * Метод можно использовать только в рамках истории по SPA WA! Движение назад
94
- * server-side переходом на несколько шагов не поддерживается.
93
+ * До версии `1.4.0` метод назывался `goBackAFewStepsClientSide` и его можно
94
+ * было использовать только в рамках SPA истории.
95
95
  *
96
- * Снять это ограничение возможно, но нужны доработки.
96
+ * После переименования этот метод можно использовать без ограничей, НО С УСЛОВИЕМ,
97
+ * что в потребителях B2N нет прямых вызовов `history.replaceState` (или оберток над ним,
98
+ * типа `history.replace` из ReactRouter). А вместо прямых вызовов используется
99
+ * метод B2N `replaceHistoryState`. Который под капотом вызывает `history.replaceState`
100
+ * или вашу собственную «обертку» над ним (см. `browserHistoryApiWrappers` в конструкторе).
101
+ *
102
+ * Если это условие не соблюдается, как и раньше, метод можно использовать
103
+ * только в рамках истории по SPA WA.
97
104
  *
98
105
  * @param stepsNumber Количество шагов назад.
99
106
  * Возможно передача как положительного, так и отрицательного числа. `0` будет проигнорирован.
100
107
  * @param autoCloseWebview Флаг — закрывать ли WV автоматически,
101
108
  * если переданное кол-во шагов будет больше, чем записей в истории.
102
109
  */
110
+ goBackAFewSteps(stepsNumber: number, autoCloseWebview?: boolean): void;
111
+ /**
112
+ * @deprecated Используйте `goBackAFewSteps`.
113
+ */
103
114
  goBackAFewStepsClientSide(stepsNumber: number, autoCloseWebview?: boolean): void;
104
115
  /**
105
116
  * Вызывает обработчик диплинков NA для обработки переданного диплинка.
@@ -138,6 +149,7 @@ export declare class BridgeToNative {
138
149
  *
139
150
  * @param url URL для перехода внутри WA client-side навигацией.
140
151
  * @param state <https://developer.mozilla.org/en-US/docs/Web/API/History/state> для новой записи в истории.
152
+ * Должен быть объектом или `null`. Примитивы (строка, число и т.п.) будут потеряны.
141
153
  * @param nativeTitle Текст заголовка, для «нативной» части WV, пустая строка — отсутствие заголовка.
142
154
  */
143
155
  navigateClientSide(url: HistoryPushStateParams[2], state?: HistoryPushStateParams[0], nativeTitle?: string): void;
@@ -147,15 +159,13 @@ export declare class BridgeToNative {
147
159
  * экзепляру B2N следующей страницы текущего WA (в случае multi-page application).
148
160
  *
149
161
  * ВАЖНО!
162
+ * С версии `1.4.0` появилась возможность использовать `goBackAFewSteps`
163
+ * через границу server-side (hard) навигации (включая переходы на другие origin).
164
+ * Но с условием, см. описание `goBackAFewSteps`.
150
165
  *
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
- * Снять эти ограничения возможно, но нужны доработки.
166
+ * ВАЖНО!
167
+ * До версии `1.4.0` метод имел ограничения, которые останутся, если переходить на WA,
168
+ * которое использует B2N версии < `1.4.0`.
159
169
  *
160
170
  * @param url URL для перехода внутри WA server-side навигацией.
161
171
  * @param nativeTitle Текст заголовка, для «нативной» части WV, пустая строка — отсутствие заголовка.
@@ -204,12 +214,20 @@ export declare class BridgeToNative {
204
214
  */
205
215
  openPdf(url: string, type?: PdfType, title?: string): void;
206
216
  /**
207
- * Для перезагрузки страницы необходимо использовать этот метод.
208
- * Иначе синхронизация состояния с NA будет потеряна.
209
- * По умолчанию метод сам делает location.reload,
210
- * но с помощью аргумента можно отключить этот вызов
217
+ * @deprecated Используйте `window.location.reload()` напрямую.
218
+ * B2N больше не требует специального метода для перезагрузки.
211
219
  */
212
220
  reload(skipReload?: boolean): void;
221
+ /**
222
+ * Позволяет изменить `history.state` и/или URL текущей записи без потери служебного свойства B2N.
223
+ * Используйте этот метод вместо прямого вызова `history.replaceState`.
224
+ * Прямой вызов `history.replaceState` приведёт к потере `b2n-pageId` и нарушит работу навигации.
225
+ *
226
+ * @param url URL для замены.
227
+ * @param state <https://developer.mozilla.org/en-US/docs/Web/API/History/state>.
228
+ * Должен быть объектом или `null`. Примитивы (строка, число и т.п.) будут потеряны.
229
+ */
230
+ replaceHistoryState(url?: HistoryReplaceStateParams[2], state?: HistoryReplaceStateParams[0]): void;
213
231
  /**
214
232
  * Информирует NA, что WA находится на первом экране. Это приведёт к тому,
215
233
  * что следующее нажатие на кнопку «Назад» в NA закроет WV.
@@ -116,18 +116,31 @@ class BridgeToNative {
116
116
  * чтобы в дальнейшем зарегистрировать этот переход в NA.
117
117
  *
118
118
  * ВАЖНО!
119
- * Метод можно использовать только в рамках истории по SPA WA! Движение назад
120
- * server-side переходом на несколько шагов не поддерживается.
119
+ * До версии `1.4.0` метод назывался `goBackAFewStepsClientSide` и его можно
120
+ * было использовать только в рамках SPA истории.
121
121
  *
122
- * Снять это ограничение возможно, но нужны доработки.
122
+ * После переименования этот метод можно использовать без ограничей, НО С УСЛОВИЕМ,
123
+ * что в потребителях B2N нет прямых вызовов `history.replaceState` (или оберток над ним,
124
+ * типа `history.replace` из ReactRouter). А вместо прямых вызовов используется
125
+ * метод B2N `replaceHistoryState`. Который под капотом вызывает `history.replaceState`
126
+ * или вашу собственную «обертку» над ним (см. `browserHistoryApiWrappers` в конструкторе).
127
+ *
128
+ * Если это условие не соблюдается, как и раньше, метод можно использовать
129
+ * только в рамках истории по SPA WA.
123
130
  *
124
131
  * @param stepsNumber Количество шагов назад.
125
132
  * Возможно передача как положительного, так и отрицательного числа. `0` будет проигнорирован.
126
133
  * @param autoCloseWebview Флаг — закрывать ли WV автоматически,
127
134
  * если переданное кол-во шагов будет больше, чем записей в истории.
128
135
  */
136
+ goBackAFewSteps(stepsNumber, autoCloseWebview = false) {
137
+ this.nativeNavigationAndTitleService.goBackAFewSteps(stepsNumber, autoCloseWebview);
138
+ }
139
+ /**
140
+ * @deprecated Используйте `goBackAFewSteps`.
141
+ */
129
142
  goBackAFewStepsClientSide(stepsNumber, autoCloseWebview = false) {
130
- this.nativeNavigationAndTitleService.goBackAFewStepsClientSide(stepsNumber, autoCloseWebview);
143
+ this.goBackAFewSteps(stepsNumber, autoCloseWebview);
131
144
  }
132
145
  /**
133
146
  * Вызывает обработчик диплинков NA для обработки переданного диплинка.
@@ -170,6 +183,7 @@ class BridgeToNative {
170
183
  *
171
184
  * @param url URL для перехода внутри WA client-side навигацией.
172
185
  * @param state <https://developer.mozilla.org/en-US/docs/Web/API/History/state> для новой записи в истории.
186
+ * Должен быть объектом или `null`. Примитивы (строка, число и т.п.) будут потеряны.
173
187
  * @param nativeTitle Текст заголовка, для «нативной» части WV, пустая строка — отсутствие заголовка.
174
188
  */
175
189
  navigateClientSide(url, state, nativeTitle = '') {
@@ -181,15 +195,13 @@ class BridgeToNative {
181
195
  * экзепляру B2N следующей страницы текущего WA (в случае multi-page application).
182
196
  *
183
197
  * ВАЖНО!
198
+ * С версии `1.4.0` появилась возможность использовать `goBackAFewSteps`
199
+ * через границу server-side (hard) навигации (включая переходы на другие origin).
200
+ * Но с условием, см. описание `goBackAFewSteps`.
184
201
  *
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
- * Снять эти ограничения возможно, но нужны доработки.
202
+ * ВАЖНО!
203
+ * До версии `1.4.0` метод имел ограничения, которые останутся, если переходить на WA,
204
+ * которое использует B2N версии < `1.4.0`.
193
205
  *
194
206
  * @param url URL для перехода внутри WA server-side навигацией.
195
207
  * @param nativeTitle Текст заголовка, для «нативной» части WV, пустая строка — отсутствие заголовка.
@@ -246,13 +258,26 @@ class BridgeToNative {
246
258
  this.externalLinksService.openPdf(url, type, title);
247
259
  }
248
260
  /**
249
- * Для перезагрузки страницы необходимо использовать этот метод.
250
- * Иначе синхронизация состояния с NA будет потеряна.
251
- * По умолчанию метод сам делает location.reload,
252
- * но с помощью аргумента можно отключить этот вызов
261
+ * @deprecated Используйте `window.location.reload()` напрямую.
262
+ * B2N больше не требует специального метода для перезагрузки.
253
263
  */
264
+ // eslint-disable-next-line class-methods-use-this -- метод оставлен для обратной совместимости.
254
265
  reload(skipReload) {
255
- this.nativeNavigationAndTitleService.reload(skipReload);
266
+ if (!skipReload) {
267
+ window.location.reload();
268
+ }
269
+ }
270
+ /**
271
+ * Позволяет изменить `history.state` и/или URL текущей записи без потери служебного свойства B2N.
272
+ * Используйте этот метод вместо прямого вызова `history.replaceState`.
273
+ * Прямой вызов `history.replaceState` приведёт к потере `b2n-pageId` и нарушит работу навигации.
274
+ *
275
+ * @param url URL для замены.
276
+ * @param state <https://developer.mozilla.org/en-US/docs/Web/API/History/state>.
277
+ * Должен быть объектом или `null`. Примитивы (строка, число и т.п.) будут потеряны.
278
+ */
279
+ replaceHistoryState(url, state = null) {
280
+ this.nativeNavigationAndTitleService.replaceHistoryState(url, state);
256
281
  }
257
282
  /**
258
283
  * Информирует NA, что WA находится на первом экране. Это приведёт к тому,
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ExternalLinksService = void 0;
4
4
  const query_and_headers_keys_1 = require("../../query-and-headers-keys");
5
5
  const constants_1 = require("../constants");
6
- const close_webview_util_1 = require("./close-webview-util");
6
+ const utils_1 = require("./utils");
7
7
  const QUERY_OPEN_IN_BROWSER_KEY = 'openInBrowser';
8
8
  const QUERY_OPEN_IN_BROWSER_VALUE = 'true';
9
9
  /**
@@ -16,15 +16,19 @@ class ExternalLinksService {
16
16
  }
17
17
  handleNativeDeeplink(deeplink, closeWebviewBeforeCallNativeDeeplinkHandler = false) {
18
18
  const clearedDeeplinkPath = deeplink.replace(constants_1.DEEP_LINK_PATTERN, '');
19
+ const originalNativeUrl = `${this.nativeParamsService.appId}://${clearedDeeplinkPath}`;
20
+ const preparedNativeUrl = this.nativeParamsService.environment === 'ios'
21
+ ? (0, utils_1.appendFromCurrentQueryParamForIos)(originalNativeUrl)
22
+ : originalNativeUrl;
19
23
  if (closeWebviewBeforeCallNativeDeeplinkHandler &&
20
24
  this.nativeParamsService.canUseNativeFeature('savedBackStack')) {
21
- (0, close_webview_util_1.closeWebviewUtil)();
25
+ (0, utils_1.closeWebviewUtil)();
22
26
  // Проверено, ОС получает диплинк и передаёт его NA, не смотря на то,
23
27
  // что это происходит в следующей макрозадаче после команды на закрытие WV.
24
- setTimeout(() => window.location.replace(`${this.nativeParamsService.appId}://${clearedDeeplinkPath}`), 0);
28
+ setTimeout(() => window.location.replace(preparedNativeUrl), 0);
25
29
  return;
26
30
  }
27
- window.location.replace(`${this.nativeParamsService.appId}://${clearedDeeplinkPath}`);
31
+ window.location.replace(preparedNativeUrl);
28
32
  }
29
33
  getHrefToOpenInBrowser(link) {
30
34
  if (!this.nativeParamsService.canUseNativeFeature('linksInBrowser')) {
@@ -1,24 +1,28 @@
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?;
14
+ private lastSetPageSettingsParams;
12
15
  private nativeHistoryStack;
13
16
  private numOfBackSteps;
14
- private lastSetPageSettingsParams;
17
+ private isGoBackLocked;
18
+ private isNavigateServerSideLocked;
15
19
  constructor(nativeParamsService: NativeParamsService, browserHistoryApiWrappers?: BrowserHistoryApiWrappers | undefined, logError?: LogError | undefined);
16
20
  closeWebview(): void;
17
21
  goBack(): void;
18
- goBackAFewStepsClientSide(stepsNumber: number, autoCloseWebview?: boolean): void;
22
+ goBackAFewSteps(stepsNumber: number, autoCloseWebview?: boolean): void;
19
23
  navigateClientSide(url: HistoryPushStateParams[2], state?: HistoryPushStateParams[0], nativeTitle?: string): void;
20
24
  navigateServerSide(link: LocationAssignParam, nativeTitle?: string): void;
21
- reload(skipReload?: boolean): void;
25
+ replaceHistoryState(url?: HistoryPushStateParams[2], state?: HistoryPushStateParams[0]): void;
22
26
  setInitialView(nativeTitle?: string): void;
23
27
  setTitle(nativeTitle: string): void;
24
28
  /**
@@ -34,14 +38,11 @@ export declare class NativeNavigationAndTitleService {
34
38
  * после нажатия на кнопку «Назад» в NA, вызова `history.back()` и `history.go(-x)`.
35
39
  */
36
40
  private handleClientSideNavigationBack;
37
- private static hasSavedHistoryStack;
38
- /**
39
- * Инициализирует `nativeHistoryStack`, учитывая варианты:
40
- * - Инициализация при открытии WV (Сценарий 1);
41
- * - Инициализация при server-side навигации в рамках одной WV-сессии (Сценарий 2);
42
- * - Инициализация при server-side переходе «назад» по истории (Сценарий 3).
43
- */
41
+ private hasSavedHistoryStack;
44
42
  private initializeNativeHistoryStack;
43
+ private initializeForBackward;
44
+ private initializeForNewOrigin;
45
+ private initializeForForward;
45
46
  /**
46
47
  * Подготавливает ссылку для корректного перехода server-side навигацией.
47
48
  *
@@ -50,22 +51,17 @@ export declare class NativeNavigationAndTitleService {
50
51
  * экзепляра B2N следующей страницы текущего WA
51
52
  */
52
53
  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
54
  /**
63
- * Сохранение состояния связи текущего WA с NA при server-side навигации в sessionStorage.
55
+ * Читает и парсит `nativeHistoryStack` из SessionStorage.
56
+ * При ошибке чтения или парсинга логирует через `logError` и пробрасывает исключение.
64
57
  */
58
+ private readSavedHistoryStack;
65
59
  private saveNativeHistoryStack;
66
60
  /**
67
61
  * Синхронизирует состояние истории переходов и заголовок с NA в соответствии
68
62
  * с `nativeHistoryStack`.
69
63
  */
70
64
  private syncHistoryWithNative;
65
+ private setHistoryStatePageId;
66
+ private createStateWithPageId;
71
67
  }
@@ -1,35 +1,54 @@
1
1
  "use strict";
2
- /* eslint max-lines: ["error", {"skipComments": true}] */ // Много комментариев.
2
+ /* eslint max-lines: ["error", {"max": 350, "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
- const close_webview_util_1 = require("./close-webview-util");
6
+ const utils_1 = require("./utils");
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
+ // Поле для фолбэк сценария, чтобы не ломать обратную совместимость в версии `1.4.0`.
24
+ // Фолбэк сцерий используется для потребителей B2N, которые используют
25
+ // прямые вызовы `history.replaceState` (или обертки над ним, типа `history.replace` из ReactRouter)
26
+ // вместо нового метода B2N `replaceHistoryState`
27
+ this.numOfBackSteps = 1;
28
+ // Предотвращают повторный вызов навигации, пока текущая не завершена.
29
+ // Без блокировки WV-браузер продолжит показывать исходную страницу,
30
+ // и повторный вызов может инициировать нежелательную навигацию.
31
+ // `isGoBackLocked` снимается в `handleClientSideNavigationBack` (popstate) для soft-навигации;
32
+ // при hard-навигации блокировка снимется автоматически при новой инициализации.
33
+ this.isGoBackLocked = false;
34
+ // `isNavigateServerSideLocked` не снимается — после server-side навигации всегда новая инициализация.
35
+ this.isNavigateServerSideLocked = false;
21
36
  this.handleClientSideNavigationBack = this.handleClientSideNavigationBack.bind(this);
22
37
  window.addEventListener('popstate', this.handleClientSideNavigationBack); // без отписки т.к. Сервис используется в течение всей жизни WA
23
38
  this.initializeNativeHistoryStack();
24
39
  }
25
40
  // eslint-disable-next-line class-methods-use-this -- удобней использовать метод в контексте экземпляра.
26
41
  closeWebview() {
27
- (0, close_webview_util_1.closeWebviewUtil)();
42
+ (0, utils_1.closeWebviewUtil)();
28
43
  }
29
44
  goBack() {
30
- this.goBackAFewStepsClientSide(-1, true);
45
+ if (this.isGoBackLocked) {
46
+ return;
47
+ }
48
+ this.isGoBackLocked = true;
49
+ this.goBackAFewSteps(-1, true);
31
50
  }
32
- goBackAFewStepsClientSide(stepsNumber, autoCloseWebview = false) {
51
+ goBackAFewSteps(stepsNumber, autoCloseWebview = false) {
33
52
  var _a;
34
53
  if (!stepsNumber) {
35
54
  return;
@@ -38,7 +57,7 @@ class NativeNavigationAndTitleService {
38
57
  const maxStepsToBack = this.nativeHistoryStack.length - 1;
39
58
  if (stepsToBack > maxStepsToBack) {
40
59
  if (autoCloseWebview) {
41
- (0, close_webview_util_1.closeWebviewUtil)();
60
+ (0, utils_1.closeWebviewUtil)();
42
61
  return;
43
62
  }
44
63
  this.numOfBackSteps = maxStepsToBack;
@@ -56,41 +75,49 @@ class NativeNavigationAndTitleService {
56
75
  // Далее сработает подписка на `popstate`, см. метод `handleClientSideNavigationBack`.
57
76
  }
58
77
  navigateClientSide(url, state = null, nativeTitle = '') {
59
- var _a, _b;
78
+ var _a;
60
79
  if ((_a = this.browserHistoryApiWrappers) === null || _a === void 0 ? void 0 : _a.push) {
61
- (_b = this.browserHistoryApiWrappers) === null || _b === void 0 ? void 0 : _b.push(url, state);
80
+ this.browserHistoryApiWrappers.push(url, state);
62
81
  }
63
82
  else {
64
83
  window.history.pushState(state, '', url);
65
84
  }
85
+ this.setHistoryStatePageId(true);
66
86
  this.nativeHistoryStack.push(nativeTitle);
87
+ this.saveNativeHistoryStack();
67
88
  this.syncHistoryWithNative();
68
89
  }
69
90
  navigateServerSide(link, nativeTitle = '') {
91
+ if (this.isNavigateServerSideLocked) {
92
+ return;
93
+ }
94
+ this.isNavigateServerSideLocked = true;
70
95
  const url = link instanceof URL ? link : new URL(link);
96
+ this.nativeHistoryStack.push(nativeTitle || '');
71
97
  if (nativeTitle) {
72
98
  url.searchParams.set(query_and_headers_keys_1.QUERY_B2N_TITLE, nativeTitle);
73
99
  }
74
- // TODO: Предыдущая реализация на iOS открывала новое WV. Возможно, что-то плохо работало,
75
- // обязательно протестировать.
76
100
  this.saveNativeHistoryStack();
77
101
  window.location.assign(this.prepareExternalLinkBeforeOpen(url));
78
102
  }
79
- reload(skipReload = false) {
80
- this.nativeHistoryStack.push(1 /* NativeHistoryStackSpecialValues.TemporaryReloadStub */); // небольшой костыль, чтобы переиспользовать server-side сценарий
81
- this.saveNativeHistoryStack();
82
- // информация для серверной стороны B2N, что происходит `reload` и парсить запрос на предмет NA параметров не нужно (в нем их скорее всего не будет)
83
- document.cookie = `${query_and_headers_keys_1.COOKIE_KEY_BRIDGE_TO_NATIVE_RELOAD}=true; Path=/`;
84
- if (!skipReload) {
85
- window.location.reload();
103
+ replaceHistoryState(url, state = null) {
104
+ var _a;
105
+ if ((_a = this.browserHistoryApiWrappers) === null || _a === void 0 ? void 0 : _a.replace) {
106
+ this.browserHistoryApiWrappers.replace(url, state);
86
107
  }
108
+ else {
109
+ window.history.replaceState(state, '', url);
110
+ }
111
+ this.setHistoryStatePageId();
87
112
  }
88
113
  setInitialView(nativeTitle = '') {
89
114
  this.nativeHistoryStack = [nativeTitle];
115
+ this.saveNativeHistoryStack();
90
116
  this.syncHistoryWithNative();
91
117
  }
92
118
  setTitle(nativeTitle) {
93
119
  this.nativeHistoryStack[this.nativeHistoryStack.length - 1] = nativeTitle;
120
+ this.saveNativeHistoryStack();
94
121
  this.syncHistoryWithNative();
95
122
  }
96
123
  /**
@@ -115,52 +142,81 @@ class NativeNavigationAndTitleService {
115
142
  * Обработчик для `window.onpopstate` события. Который сработает
116
143
  * после нажатия на кнопку «Назад» в NA, вызова `history.back()` и `history.go(-x)`.
117
144
  */
118
- handleClientSideNavigationBack() {
119
- this.nativeHistoryStack = this.nativeHistoryStack.slice(0, -this.numOfBackSteps);
145
+ handleClientSideNavigationBack(event) {
146
+ var _a;
147
+ this.isGoBackLocked = false;
148
+ 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];
149
+ if (typeof statePageId === 'number') {
150
+ this.nativeHistoryStack = this.nativeHistoryStack.slice(0, statePageId);
151
+ }
152
+ else {
153
+ this.nativeHistoryStack = this.nativeHistoryStack.slice(0, -this.numOfBackSteps);
154
+ }
120
155
  this.numOfBackSteps = 1;
121
156
  if (this.nativeHistoryStack.length < 1) {
122
- (0, close_webview_util_1.closeWebviewUtil)();
157
+ (0, utils_1.closeWebviewUtil)();
123
158
  return;
124
159
  }
160
+ this.saveNativeHistoryStack();
125
161
  this.syncHistoryWithNative();
126
162
  }
127
- static hasSavedHistoryStack() {
163
+ // eslint-disable-next-line class-methods-use-this -- удобней использовать метод в контексте экземпляра.
164
+ hasSavedHistoryStack() {
128
165
  return sessionStorage.getItem(query_and_headers_keys_1.SS_KEY_BRIDGE_TO_NATIVE_HISTORY_STACK) !== null;
129
166
  }
130
- /**
131
- * Инициализирует `nativeHistoryStack`, учитывая варианты:
132
- * - Инициализация при открытии WV (Сценарий 1);
133
- * - Инициализация при server-side навигации в рамках одной WV-сессии (Сценарий 2);
134
- * - Инициализация при server-side переходе «назад» по истории (Сценарий 3).
135
- */
136
167
  initializeNativeHistoryStack() {
168
+ var _a;
137
169
  const { nextPageId, title } = this.nativeParamsService;
138
- if (nextPageId &&
139
- NativeNavigationAndTitleService.shouldInitializeFromNextPageId(nextPageId)) {
140
- // Сценарий 2 – `nextPageId` ставит метод `this.navigateServerSide`,
141
- // т.е. это инициализация сразу после перехода server-side навигацией.
142
- // Если в sessionStorage уже есть стек, используем `nextPageId` только для прямого
143
- // перехода "вперёд", когда он на 1 больше сохранённой глубины истории.
144
- this.nativeHistoryStack = new Array(nextPageId).fill(0 /* NativeHistoryStackSpecialValues.ServerSideNavigationStub */);
145
- this.nativeHistoryStack[this.nativeHistoryStack.length - 1] = title;
146
- }
147
- else if (NativeNavigationAndTitleService.hasSavedHistoryStack()) {
148
- // Сценарий 3 - в sessionStorage есть сохранённый nativeHistoryStack,
149
- // значит это инициализация сразу после перехода назад server-side навигацией,
150
- // или инициализация после использования метода `reload`.
151
- try {
152
- this.nativeHistoryStack = this.readAndUpdateNativeHistoryStackSessionStorage();
170
+ const hasSS = this.hasSavedHistoryStack();
171
+ const statePageId = (_a = window.history.state) === null || _a === void 0 ? void 0 : _a[query_and_headers_keys_1.HISTORY_STATE_KEY_B2N_PAGE_ID];
172
+ try {
173
+ if (typeof statePageId === 'number') {
174
+ this.nativeHistoryStack = this.initializeForBackward(statePageId, title);
153
175
  }
154
- catch (_a) {
155
- this.nativeHistoryStack = [''];
176
+ else if (nextPageId && !hasSS) {
177
+ this.nativeHistoryStack = this.initializeForNewOrigin(nextPageId, title);
178
+ }
179
+ else if (nextPageId && hasSS) {
180
+ this.nativeHistoryStack = this.initializeForForward(nextPageId, title);
181
+ }
182
+ else {
183
+ this.nativeHistoryStack = [title];
156
184
  }
157
185
  }
158
- else {
159
- // Сценарий 1 - запись в sessionStorage ставит метод `this.navigateServerSide`,
160
- // её нет, значит это инициализация сразу после открытия нового WV.
186
+ catch (_b) {
161
187
  this.nativeHistoryStack = [title];
162
188
  }
189
+ this.saveNativeHistoryStack();
163
190
  this.syncHistoryWithNative();
191
+ this.setHistoryStatePageId();
192
+ }
193
+ initializeForBackward(pageId, title) {
194
+ if (!this.hasSavedHistoryStack()) {
195
+ return [title];
196
+ }
197
+ const savedStack = this.readSavedHistoryStack();
198
+ const stack = savedStack.slice(0, pageId);
199
+ stack[stack.length - 1] = title;
200
+ return stack;
201
+ }
202
+ // eslint-disable-next-line class-methods-use-this -- удобней использовать метод в контексте экземпляра.
203
+ initializeForNewOrigin(nextPageId, title) {
204
+ const stack = new Array(nextPageId).fill(NativeHistoryStackStub);
205
+ stack[stack.length - 1] = title;
206
+ return stack;
207
+ }
208
+ initializeForForward(nextPageId, title) {
209
+ const savedStack = this.readSavedHistoryStack();
210
+ if (savedStack.length === nextPageId) {
211
+ savedStack[savedStack.length - 1] = title;
212
+ return savedStack;
213
+ }
214
+ const stack = new Array(nextPageId).fill(NativeHistoryStackStub);
215
+ for (let i = 0; i < savedStack.length && i < nextPageId; i++) {
216
+ stack[i] = savedStack[i];
217
+ }
218
+ stack[stack.length - 1] = title;
219
+ return stack;
164
220
  }
165
221
  /**
166
222
  * Подготавливает ссылку для корректного перехода server-side навигацией.
@@ -183,59 +239,29 @@ class NativeNavigationAndTitleService {
183
239
  // Таким образом гарантируется, что версию приложения будет видеть следующее WA
184
240
  // (заголовок `app-version` может отсутствовать при server-side переходах).
185
241
  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, (currentPageId + 1).toString());
242
+ modifiedUrl.searchParams.set(query_and_headers_keys_1.QUERY_B2N_NEXT_PAGEID, currentPageId.toString());
187
243
  return modifiedUrl;
188
244
  }
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
245
  /**
207
- * Читает сохраннённый в sessionStorage `nativeHistoryStack`,
208
- * снова сохраняет его в sessionStorage, уменьшая список на одну запись,
209
- * на случай, если будет дальнейший переход назад server-side навигацией.
210
- *
211
- * @returns Актуальное состояние `nativeHistoryStack` из sessionStorage.
246
+ * Читает и парсит `nativeHistoryStack` из SessionStorage.
247
+ * При ошибке чтения или парсинга логирует через `logError` и пробрасывает исключение.
212
248
  */
213
- readAndUpdateNativeHistoryStackSessionStorage() {
249
+ readSavedHistoryStack() {
250
+ const serialized = sessionStorage.getItem(query_and_headers_keys_1.SS_KEY_BRIDGE_TO_NATIVE_HISTORY_STACK);
214
251
  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();
252
+ if (!serialized) {
253
+ throw new Error(`${query_and_headers_keys_1.SS_KEY_BRIDGE_TO_NATIVE_HISTORY_STACK} sessionStorage expected not to be null`);
218
254
  }
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;
255
+ return JSON.parse(serialized);
227
256
  }
228
257
  catch (e) {
229
258
  if (this.logError) {
230
- this.logError('Клиентский код B2N не смог восстановить `nativeHistoryStack` из sessionStorage. ' +
231
- 'Могут возникнуть проблемы с кнопкой «Назад» в NA.', e);
259
+ this.logError(`Клиентский код B2N не смог получить ${query_and_headers_keys_1.SS_KEY_BRIDGE_TO_NATIVE_HISTORY_STACK} из sessionStorage
260
+ Могут возникнуть проблемы с кнопкой «Назад» в NA.`, e);
232
261
  }
233
- throw new Error();
262
+ throw e;
234
263
  }
235
264
  }
236
- /**
237
- * Сохранение состояния связи текущего WA с NA при server-side навигации в sessionStorage.
238
- */
239
265
  saveNativeHistoryStack() {
240
266
  const serializedNativeHistoryStack = JSON.stringify(this.nativeHistoryStack);
241
267
  sessionStorage.setItem(query_and_headers_keys_1.SS_KEY_BRIDGE_TO_NATIVE_HISTORY_STACK, serializedNativeHistoryStack);
@@ -265,5 +291,20 @@ class NativeNavigationAndTitleService {
265
291
  }
266
292
  }
267
293
  }
294
+ setHistoryStatePageId(useNextPageId = false) {
295
+ const pageId = useNextPageId
296
+ ? this.nativeHistoryStack.length + 1
297
+ : this.nativeHistoryStack.length;
298
+ const newState = this.createStateWithPageId(window.history.state, pageId);
299
+ // `b2n-pageId` всегда записывается на верхний уровень через нативный `replaceState`,
300
+ // а не через wrapper, чтобы wrapper (например, React Router / history) не мог обернуть
301
+ // его внутрь своего формата и скрыть от B2N при чтении `history.state`.
302
+ window.history.replaceState(newState, '');
303
+ }
304
+ // eslint-disable-next-line class-methods-use-this -- удобней использовать метод в контексте экземпляра.
305
+ createStateWithPageId(state, pageId) {
306
+ const isPlainObject = (v) => v !== null && v !== undefined && typeof v === 'object' && !Array.isArray(v);
307
+ return Object.assign(Object.assign({}, (isPlainObject(state) ? state : {})), { [query_and_headers_keys_1.HISTORY_STATE_KEY_B2N_PAGE_ID]: pageId });
308
+ }
268
309
  }
269
310
  exports.NativeNavigationAndTitleService = NativeNavigationAndTitleService;
@@ -0,0 +1,2 @@
1
+ export declare function appendFromCurrentQueryParamForIos(nativeUrl: string): string;
2
+ export declare const closeWebviewUtil: () => void;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.closeWebviewUtil = void 0;
4
+ exports.appendFromCurrentQueryParamForIos = appendFromCurrentQueryParamForIos;
5
+ const QUERY_CLOSE_WEBVIEW_KEY = 'closeWebView';
6
+ const QUERY_CLOSE_WEBVIEW_VALUE = 'true';
7
+ const QUERY_FROM_CURRENT_KEY = 'fromCurrent';
8
+ const QUERY_FROM_CURRENT_VALUE = 'true';
9
+ function appendFromCurrentQueryParamForIos(nativeUrl) {
10
+ const qIndex = nativeUrl.indexOf('?');
11
+ if (qIndex === -1) {
12
+ return `${nativeUrl}?${QUERY_FROM_CURRENT_KEY}=${QUERY_FROM_CURRENT_VALUE}`;
13
+ }
14
+ const base = nativeUrl.slice(0, qIndex);
15
+ const query = nativeUrl.slice(qIndex + 1);
16
+ const params = new URLSearchParams(query);
17
+ params.set(QUERY_FROM_CURRENT_KEY, QUERY_FROM_CURRENT_VALUE);
18
+ return `${base}?${params.toString()}`;
19
+ }
20
+ const closeWebviewUtil = () => {
21
+ const originalPageUrl = new URL(window.location.href);
22
+ originalPageUrl.searchParams.set(QUERY_CLOSE_WEBVIEW_KEY, QUERY_CLOSE_WEBVIEW_VALUE);
23
+ window.location.href = originalPageUrl.toString();
24
+ };
25
+ exports.closeWebviewUtil = closeWebviewUtil;
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
@@ -1,121 +1,121 @@
1
1
  {
2
- "name": "@alfalab/bridge-to-native",
3
- "version": "1.3.2",
4
- "license": "MIT",
5
- "description": "Утилита для удобной работы веб приложения внутри нативного приложения и коммуникации с ним.",
6
- "engines": {
7
- "node": ">=20.19.2",
8
- "npm": "please-use-yarn"
2
+ "name": "@alfalab/bridge-to-native",
3
+ "version": "1.4.0-beta.560edfc",
4
+ "license": "MIT",
5
+ "description": "Утилита для удобной работы веб приложения внутри нативного приложения и коммуникации с ним.",
6
+ "engines": {
7
+ "node": ">=20.19.2",
8
+ "npm": "please-use-yarn"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/core-ds/bridge-to-native.git"
13
+ },
14
+ "exports": {
15
+ "./client": {
16
+ "import": "./client/index.js",
17
+ "require": "./client/index.js",
18
+ "types": "./client/index.d.ts"
9
19
  },
10
- "repository": {
11
- "type": "git",
12
- "url": "git+https://github.com/core-ds/bridge-to-native.git"
20
+ "./server": {
21
+ "import": "./server/index.js",
22
+ "require": "./server/index.js",
23
+ "types": "./server/index.d.ts"
24
+ }
25
+ },
26
+ "sideEffects": false,
27
+ "bugs": {
28
+ "url": "https://github.com/core-ds/bridge-to-native/issues"
29
+ },
30
+ "scripts": {
31
+ "build": "yarn build:clean && yarn build:ts",
32
+ "build:clean": "shx rm -rf .publish",
33
+ "build:copy-package-json": "shx cp package.json .publish/package.json",
34
+ "build:ts": "tsc --build",
35
+ "format": "arui-presets-lint format",
36
+ "format:check": "arui-presets-lint format:check",
37
+ "lint": "yarn lint:scripts && yarn format:check",
38
+ "lint:fix": "yarn lint:scripts --fix && yarn format",
39
+ "lint:scripts": "arui-presets-lint scripts",
40
+ "pub": "npm publish .publish --userconfig \"../.npmrc\" --tag \"$TAG\"",
41
+ "release": "yarn build && yarn build:copy-package-json && yarn pub",
42
+ "test": "arui-scripts test --silent --collect-coverage"
43
+ },
44
+ "devDependencies": {
45
+ "@happy-dom/jest-environment": "18.0.1",
46
+ "@types/jest": "29.5.14",
47
+ "@types/node": "20.19.1",
48
+ "arui-presets-lint": "8.7.0",
49
+ "arui-scripts": "19.0.7",
50
+ "copyfiles": "2.4.1",
51
+ "jest-junit": "10.0.0",
52
+ "lint-staged": "^12.5.0",
53
+ "promisify-child-process": "4.1.1",
54
+ "shx": "0.3.4",
55
+ "typescript": "5.5.4"
56
+ },
57
+ "commitlint": {
58
+ "extends": "./node_modules/arui-presets-lint/commitlint"
59
+ },
60
+ "eslintConfig": {
61
+ "extends": "./node_modules/arui-presets-lint/eslint",
62
+ "parserOptions": {
63
+ "project": [
64
+ "./__tests__/tsconfig.json",
65
+ "./src/client/tsconfig.json",
66
+ "./src/server/tsconfig.json"
67
+ ]
13
68
  },
14
- "exports": {
15
- "./client": {
16
- "import": "./client/index.js",
17
- "require": "./client/index.js",
18
- "types": "./client/index.d.ts"
19
- },
20
- "./server": {
21
- "import": "./server/index.js",
22
- "require": "./server/index.js",
23
- "types": "./server/index.d.ts"
24
- }
25
- },
26
- "sideEffects": false,
27
- "bugs": {
28
- "url": "https://github.com/core-ds/bridge-to-native/issues"
29
- },
30
- "scripts": {
31
- "build": "yarn build:clean && yarn build:ts",
32
- "build:clean": "shx rm -rf .publish",
33
- "build:copy-package-json": "shx cp package.json .publish/package.json",
34
- "build:ts": "tsc --build",
35
- "format": "arui-presets-lint format",
36
- "format:check": "arui-presets-lint format:check",
37
- "lint": "yarn lint:scripts && yarn format:check",
38
- "lint:fix": "yarn lint:scripts --fix && yarn format",
39
- "lint:scripts": "arui-presets-lint scripts",
40
- "pub": "npm publish .publish --userconfig \"../.npmrc\" --tag \"$TAG\"",
41
- "release": "yarn build && yarn build:copy-package-json && yarn pub",
42
- "test": "arui-scripts test --silent --collect-coverage"
43
- },
44
- "devDependencies": {
45
- "@happy-dom/jest-environment": "18.0.1",
46
- "@types/jest": "29.5.14",
47
- "@types/node": "20.19.1",
48
- "arui-presets-lint": "8.7.0",
49
- "arui-scripts": "19.0.7",
50
- "copyfiles": "2.4.1",
51
- "jest-junit": "10.0.0",
52
- "lint-staged": "^12.5.0",
53
- "promisify-child-process": "4.1.1",
54
- "shx": "0.3.4",
55
- "typescript": "5.5.4"
56
- },
57
- "commitlint": {
58
- "extends": "./node_modules/arui-presets-lint/commitlint"
59
- },
60
- "eslintConfig": {
61
- "extends": "./node_modules/arui-presets-lint/eslint",
62
- "parserOptions": {
63
- "project": [
64
- "./__tests__/tsconfig.json",
65
- "./src/client/tsconfig.json",
66
- "./src/server/tsconfig.json"
67
- ]
68
- },
69
- "overrides": [
70
- {
71
- "files": [
72
- "__tests__/**/*"
73
- ],
74
- "rules": {
75
- "max-lines": "off"
76
- }
77
- }
78
- ]
79
- },
80
- "jest": {
81
- "projects": [
82
- {
83
- "displayName": "client-tests",
84
- "testEnvironment": "@happy-dom/jest-environment",
85
- "testPathIgnorePatterns": [
86
- "/__tests__/server/"
87
- ]
88
- },
89
- {
90
- "displayName": "server-tests",
91
- "testEnvironment": "node",
92
- "testPathIgnorePatterns": [
93
- "/__tests__/client/"
94
- ]
95
- }
96
- ],
97
- "testRegex": [
98
- "__tests__/.*\\.test\\.ts"
69
+ "overrides": [
70
+ {
71
+ "files": [
72
+ "__tests__/**/*"
99
73
  ],
100
- "coveragePathIgnorePatterns": [
101
- "/node_modules/",
102
- "/src/(client|server)/index.ts"
103
- ],
104
- "reporters": [
105
- "default",
106
- "jest-junit"
74
+ "rules": {
75
+ "max-lines": "off"
76
+ }
77
+ }
78
+ ]
79
+ },
80
+ "jest": {
81
+ "projects": [
82
+ {
83
+ "displayName": "client-tests",
84
+ "testEnvironment": "@happy-dom/jest-environment",
85
+ "testPathIgnorePatterns": [
86
+ "/__tests__/server/"
107
87
  ]
108
- },
109
- "prettier": "arui-presets-lint/prettier",
110
- "stylelint": {
111
- "extends": "arui-presets-lint/stylelint",
112
- "ignoreFiles": [
113
- "coverage/**/*.js",
114
- ".yarn/releases/*"
88
+ },
89
+ {
90
+ "displayName": "server-tests",
91
+ "testEnvironment": "node",
92
+ "testPathIgnorePatterns": [
93
+ "/__tests__/client/"
115
94
  ]
116
- },
117
- "publishConfig": {
118
- "registry": "https://registry.npmjs.org"
119
- },
120
- "packageManager": "yarn@4.12.0"
95
+ }
96
+ ],
97
+ "testRegex": [
98
+ "__tests__/.*\\.test\\.ts"
99
+ ],
100
+ "coveragePathIgnorePatterns": [
101
+ "/node_modules/",
102
+ "/src/(client|server)/index.ts"
103
+ ],
104
+ "reporters": [
105
+ "default",
106
+ "jest-junit"
107
+ ]
108
+ },
109
+ "prettier": "arui-presets-lint/prettier",
110
+ "stylelint": {
111
+ "extends": "arui-presets-lint/stylelint",
112
+ "ignoreFiles": [
113
+ "coverage/**/*.js",
114
+ ".yarn/releases/*"
115
+ ]
116
+ },
117
+ "publishConfig": {
118
+ "registry": "https://registry.npmjs.org"
119
+ },
120
+ "packageManager": "yarn@4.12.0"
121
121
  }
@@ -1,9 +1,9 @@
1
1
  export declare const HEADER_KEY_COOKIE = "cookie";
2
2
  export declare const HEADER_KEY_USER_AGENT = "user-agent";
3
3
  export declare const COOKIE_KEY_BRIDGE_TO_NATIVE_DATA = "bridgeToNativeData";
4
- export declare const COOKIE_KEY_BRIDGE_TO_NATIVE_RELOAD = "bridgeToNativeReload";
5
4
  export declare const HEADER_KEY_NATIVE_APPVERSION = "app-version";
6
5
  export declare const HEADER_KEY_WV_LAUNCH_TIME = "webview-launch-time";
6
+ export declare const HISTORY_STATE_KEY_B2N_PAGE_ID = "b2n-pageId";
7
7
  export declare const QUERY_B2N_NEXT_PAGEID = "b2n-next-page-id";
8
8
  export declare const QUERY_B2N_TITLE = "b2n-title";
9
9
  export declare const QUERY_B2N_TITLE_DEPRECATED = "title";
@@ -3,32 +3,33 @@
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_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
- * Ключи заголовков и query-параметров, которые использует B2N.
10
+ * Ключи заголовков, query-параметров и пр., которые использует B2N.
11
11
  */
12
12
  // Ключ cookie, с помощью которого серверная часть B2N передаст на клиент информацию об NA.
13
13
  exports.COOKIE_KEY_BRIDGE_TO_NATIVE_DATA = 'bridgeToNativeData';
14
- // Ключ cookie, указывающий, что запрос выполнен после вызова `bridgeToNative.reload()`.
15
- // Используется для предотвращения перезаписи cookie bridgeToNativeData, чтобы сохранить
16
- // актуальные параметры нативного приложения
17
- exports.COOKIE_KEY_BRIDGE_TO_NATIVE_RELOAD = 'bridgeToNativeReload';
18
- // Нативное приложение на обеих платформах подмешивает этот заголовок к запросу за HTML.
14
+ // NA на обеих платформах подмешивает этот заголовок к запросу за HTML.
19
15
  // TODO:
20
16
  // * Исследовать, делает ли оно это при запросах к прочим ресурсам;
21
17
  // * Есть подозрение на некоторую нестабильность — возможно делает это
22
18
  // только для первого документа в вебвью-сессии. Исследовать.
23
19
  exports.HEADER_KEY_NATIVE_APPVERSION = 'app-version';
24
- // Ключ заголовка, в котором передается время старта открытия WV экрана в формате timestamp
20
+ // Ключ заголовка, в котором NA передаёт время старта открытия WV экрана в формате timestamp.
25
21
  exports.HEADER_KEY_WV_LAUNCH_TIME = 'webview-launch-time';
22
+ // Ключ в history.state, который B2N использует для хранения текущего `pageId`
23
+ // чтобы иметь возможность корректно синхронизироваться с NA после back-навигации.
24
+ exports.HISTORY_STATE_KEY_B2N_PAGE_ID = 'b2n-pageId';
26
25
  // Флаг, предписывающий клиентскому коду B2N начать связь
27
26
  // с нативным приложением с указанного в этом параметре id, а не с 1.
28
- // Необходимо для server-side навигации.
27
+ // Необходимо для server-side (hard) навигации.
29
28
  exports.QUERY_B2N_NEXT_PAGEID = 'b2n-next-page-id';
30
- // Query-параметр, с помощью которого можно передать начальное значение
29
+ // Query-параметр, который можно подмешать к URL в диплинке на открытие WV,
30
+ // и с помощью которого можно передать начальное значение
31
31
  // для нативного заголовка — в область, которую не рендерит веб.
32
+ // Также некоторые методы навигации B2N используют эту сущность.
32
33
  exports.QUERY_B2N_TITLE = 'b2n-title';
33
34
  // Deprecated вариант query-параметра 'b2n-title'
34
35
  // (т.к. `title` часто может использоваться самим веб-приложением).
@@ -36,18 +37,18 @@ exports.QUERY_B2N_TITLE = 'b2n-title';
36
37
  exports.QUERY_B2N_TITLE_DEPRECATED = 'title';
37
38
  // NA на iOS передаёт в этом параметре схему,
38
39
  // под которым оно зарегистрировано в OS.
39
- // Известные проблемы:
40
- // * В старых версиях отсутствует, клиентский код использует хардкод (см. `src/client/constants.ts`);
41
- // * В тестовых сборках часто нарушена связь!!! Между фактической схемой
42
- // и схемой, которая приходит в этом параметре.
43
- // Нужно просить iOS разработчика собрать сборку нормально.
40
+ // Некоторые особенности:
41
+ // * В старых версиях отсутствует, клиентский код использует хардкод
42
+ // (см. `VERSION_TO_IOS_APP_ID` в `src/client/constants.ts`);
43
+ // * Тестить WV на iOS надо на группе «B»!!! Иначе NA будет присылать в WA неверную схему,
44
+ // что приведёт к неработоспособности методов, связанных с передачей диплинков в NA.
45
+ // * В сборках для Симулятора, схема всегда `aweassist`.
44
46
  exports.QUERY_NATIVE_IOS_APPID = 'applicationId';
45
- // Исторически NA на iOS передаёт в этом параметре свою версию.
46
- // B2N сознательно переиспользует этот query и для server-side переходов в Android,
47
+ // NA на iOS передаёт в этом параметре свою версию.
48
+ // B2N сознательно переиспользует этот query и для server-side (hard) переходов в Android,
47
49
  // чтобы не вводить отдельный служебный параметр только для переноса версии между WA.
48
50
  exports.QUERY_NATIVE_IOS_APPVERSION = 'device_app_version';
49
51
  // NA на обеих платформах в этом параметре передаёт активную тему (светлая/тёмная).
50
52
  exports.QUERY_NATIVE_THEME = 'theme';
51
- // Ключ sesseionStorage, с помощью которого клиентская часть B2N сохраняет своё состояние
52
- // при server-side навигации.
53
+ // Ключ sessionStorage,в котором клиентская часть B2N сохраняет состояние синхронизации с NA.
53
54
  exports.SS_KEY_BRIDGE_TO_NATIVE_HISTORY_STACK = 'bridgeToNativeHistoryStack';
@@ -19,21 +19,9 @@ const utils_1 = require("./utils");
19
19
  function prepareNativeAppDetailsForClient(request, setResponseHeader) {
20
20
  // Поскольку вебвью модули имеют особенность сохранять сессионную куку подолгу,
21
21
  // даже после перезагрузки устройства или обновления приложения/ОС, ее значение
22
- // актуализируется при каждом запросе на сервер. Исключением является вызов `reload()`.
23
- // В этом случае функция возвращает объект с параметрами, полученными из
24
- // ранее сохраненной куки, но перезаписываться кука не будет, потому что:
25
- // 1) Данных NA в запросе с большой вероятностью не будет;
26
- // 2) клиентская сторона сохранит всё, что нужно в SessionStorage
22
+ // актуализируется при каждом запросе на сервер.
27
23
  const cookieHeader = (0, utils_1.getHeaderValue)(request, query_and_headers_keys_1.HEADER_KEY_COOKIE);
28
24
  const nativeParamsFromCookie = (0, utils_1.readNativeParamsFromCookie)(cookieHeader);
29
- const hasReloadFlag = cookieHeader?.includes(`${query_and_headers_keys_1.COOKIE_KEY_BRIDGE_TO_NATIVE_RELOAD}=true`);
30
- if (hasReloadFlag) {
31
- setResponseHeader('Set-Cookie', `${query_and_headers_keys_1.COOKIE_KEY_BRIDGE_TO_NATIVE_RELOAD}=false; Max-Age=0; Path=/`);
32
- if (nativeParamsFromCookie) {
33
- return nativeParamsFromCookie;
34
- }
35
- return buildNativeParams(request);
36
- }
37
25
  const nativeParams = buildNativeParams(request, nativeParamsFromCookie);
38
26
  const serializedNativeParams = encodeURIComponent(JSON.stringify(nativeParams));
39
27
  setResponseHeader('Set-Cookie', `${query_and_headers_keys_1.COOKIE_KEY_BRIDGE_TO_NATIVE_DATA}=${serializedNativeParams}; Path=/`);
@@ -1 +0,0 @@
1
- export declare const closeWebviewUtil: () => void;
@@ -1,11 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.closeWebviewUtil = void 0;
4
- const QUERY_CLOSE_WEBVIEW_KEY = 'closeWebView';
5
- const QUERY_CLOSE_WEBVIEW_VALUE = 'true';
6
- const closeWebviewUtil = () => {
7
- const originalPageUrl = new URL(window.location.href);
8
- originalPageUrl.searchParams.set(QUERY_CLOSE_WEBVIEW_KEY, QUERY_CLOSE_WEBVIEW_VALUE);
9
- window.location.href = originalPageUrl.toString();
10
- };
11
- exports.closeWebviewUtil = closeWebviewUtil;