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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -89,11 +89,28 @@ export declare class BridgeToNative {
89
89
  * Делает несколько шагов назад по браузерной истории и модифицирует внутреннее состояние,
90
90
  * чтобы в дальнейшем зарегистрировать этот переход в NA.
91
91
  *
92
+ * ВАЖНО!
93
+ * До версии `1.4.0` метод назывался `goBackAFewStepsClientSide` и его можно
94
+ * было использовать только в рамках SPA истории.
95
+ *
96
+ * После переименования этот метод можно использовать без ограничей, НО С УСЛОВИЕМ,
97
+ * что в потребителях B2N нет прямых вызовов `history.replaceState` (или оберток над ним,
98
+ * типа `history.replace` из ReactRouter). А вместо прямых вызовов используется
99
+ * метод B2N `replaceHistoryState`. Который под капотом вызывает `history.replaceState`
100
+ * или вашу собственную «обертку» над ним (см. `browserHistoryApiWrappers` в конструкторе).
101
+ *
102
+ * Если это условие не соблюдается, как и раньше, метод можно использовать
103
+ * только в рамках истории по SPA WA.
104
+ *
92
105
  * @param stepsNumber Количество шагов назад.
93
106
  * Возможно передача как положительного, так и отрицательного числа. `0` будет проигнорирован.
94
107
  * @param autoCloseWebview Флаг — закрывать ли WV автоматически,
95
108
  * если переданное кол-во шагов будет больше, чем записей в истории.
96
109
  */
110
+ goBackAFewSteps(stepsNumber: number, autoCloseWebview?: boolean): void;
111
+ /**
112
+ * @deprecated Используйте `goBackAFewSteps`.
113
+ */
97
114
  goBackAFewStepsClientSide(stepsNumber: number, autoCloseWebview?: boolean): void;
98
115
  /**
99
116
  * Вызывает обработчик диплинков NA для обработки переданного диплинка.
@@ -141,6 +158,15 @@ export declare class BridgeToNative {
141
158
  * чтобы корректно передать информацию экземпляру B2N следующего WA или
142
159
  * экзепляру B2N следующей страницы текущего WA (в случае multi-page application).
143
160
  *
161
+ * ВАЖНО!
162
+ * С версии `1.4.0` появилась возможность использовать `goBackAFewSteps`
163
+ * через границу server-side (hard) навигации (включая переходы на другие origin).
164
+ * Но с условием, см. описание `goBackAFewSteps`.
165
+ *
166
+ * ВАЖНО!
167
+ * До версии `1.4.0` метод имел ограничения, которые останутся, если переходить на WA,
168
+ * которое использует B2N версии < `1.4.0`.
169
+ *
144
170
  * @param url URL для перехода внутри WA server-side навигацией.
145
171
  * @param nativeTitle Текст заголовка, для «нативной» части WV, пустая строка — отсутствие заголовка.
146
172
  */
@@ -188,11 +214,8 @@ export declare class BridgeToNative {
188
214
  */
189
215
  openPdf(url: string, type?: PdfType, title?: string): void;
190
216
  /**
191
- * Для перезагрузки страницы необходимо использовать этот метод.
192
- * Иначе синхронизация состояния с NA будет потеряна.
193
- *
194
- * @param skipReload По умолчанию метод сам делает `location.reload`,
195
- * но с помощью аргумента можно отключить этот вызов, если нужно.
217
+ * @deprecated Используйте `window.location.reload()` напрямую.
218
+ * B2N больше не требует специального метода для перезагрузки.
196
219
  */
197
220
  reload(skipReload?: boolean): void;
198
221
  /**
@@ -115,13 +115,32 @@ class BridgeToNative {
115
115
  * Делает несколько шагов назад по браузерной истории и модифицирует внутреннее состояние,
116
116
  * чтобы в дальнейшем зарегистрировать этот переход в NA.
117
117
  *
118
+ * ВАЖНО!
119
+ * До версии `1.4.0` метод назывался `goBackAFewStepsClientSide` и его можно
120
+ * было использовать только в рамках SPA истории.
121
+ *
122
+ * После переименования этот метод можно использовать без ограничей, НО С УСЛОВИЕМ,
123
+ * что в потребителях B2N нет прямых вызовов `history.replaceState` (или оберток над ним,
124
+ * типа `history.replace` из ReactRouter). А вместо прямых вызовов используется
125
+ * метод B2N `replaceHistoryState`. Который под капотом вызывает `history.replaceState`
126
+ * или вашу собственную «обертку» над ним (см. `browserHistoryApiWrappers` в конструкторе).
127
+ *
128
+ * Если это условие не соблюдается, как и раньше, метод можно использовать
129
+ * только в рамках истории по SPA WA.
130
+ *
118
131
  * @param stepsNumber Количество шагов назад.
119
132
  * Возможно передача как положительного, так и отрицательного числа. `0` будет проигнорирован.
120
133
  * @param autoCloseWebview Флаг — закрывать ли WV автоматически,
121
134
  * если переданное кол-во шагов будет больше, чем записей в истории.
122
135
  */
136
+ goBackAFewSteps(stepsNumber, autoCloseWebview = false) {
137
+ this.nativeNavigationAndTitleService.goBackAFewSteps(stepsNumber, autoCloseWebview);
138
+ }
139
+ /**
140
+ * @deprecated Используйте `goBackAFewSteps`.
141
+ */
123
142
  goBackAFewStepsClientSide(stepsNumber, autoCloseWebview = false) {
124
- this.nativeNavigationAndTitleService.goBackAFewStepsClientSide(stepsNumber, autoCloseWebview);
143
+ this.goBackAFewSteps(stepsNumber, autoCloseWebview);
125
144
  }
126
145
  /**
127
146
  * Вызывает обработчик диплинков NA для обработки переданного диплинка.
@@ -175,6 +194,15 @@ class BridgeToNative {
175
194
  * чтобы корректно передать информацию экземпляру B2N следующего WA или
176
195
  * экзепляру B2N следующей страницы текущего WA (в случае multi-page application).
177
196
  *
197
+ * ВАЖНО!
198
+ * С версии `1.4.0` появилась возможность использовать `goBackAFewSteps`
199
+ * через границу server-side (hard) навигации (включая переходы на другие origin).
200
+ * Но с условием, см. описание `goBackAFewSteps`.
201
+ *
202
+ * ВАЖНО!
203
+ * До версии `1.4.0` метод имел ограничения, которые останутся, если переходить на WA,
204
+ * которое использует B2N версии < `1.4.0`.
205
+ *
178
206
  * @param url URL для перехода внутри WA server-side навигацией.
179
207
  * @param nativeTitle Текст заголовка, для «нативной» части WV, пустая строка — отсутствие заголовка.
180
208
  */
@@ -230,14 +258,14 @@ class BridgeToNative {
230
258
  this.externalLinksService.openPdf(url, type, title);
231
259
  }
232
260
  /**
233
- * Для перезагрузки страницы необходимо использовать этот метод.
234
- * Иначе синхронизация состояния с NA будет потеряна.
235
- *
236
- * @param skipReload По умолчанию метод сам делает `location.reload`,
237
- * но с помощью аргумента можно отключить этот вызов, если нужно.
261
+ * @deprecated Используйте `window.location.reload()` напрямую.
262
+ * B2N больше не требует специального метода для перезагрузки.
238
263
  */
264
+ // eslint-disable-next-line class-methods-use-this -- метод оставлен для обратной совместимости.
239
265
  reload(skipReload) {
240
- this.nativeNavigationAndTitleService.reload(skipReload);
266
+ if (!skipReload) {
267
+ window.location.reload();
268
+ }
241
269
  }
242
270
  /**
243
271
  * Позволяет изменить `history.state` и/или URL текущей записи без потери служебного свойства B2N.
@@ -11,22 +11,20 @@ export declare class NativeNavigationAndTitleService {
11
11
  private nativeParamsService;
12
12
  private browserHistoryApiWrappers?;
13
13
  private logError?;
14
- private nativeHistoryStack;
15
14
  private lastSetPageSettingsParams;
15
+ private nativeHistoryStack;
16
+ private numOfBackSteps;
16
17
  private isGoBackLocked;
17
18
  private isNavigateServerSideLocked;
18
19
  constructor(nativeParamsService: NativeParamsService, browserHistoryApiWrappers?: BrowserHistoryApiWrappers | undefined, logError?: LogError | undefined);
19
20
  closeWebview(): void;
20
21
  goBack(): void;
21
- goBackAFewStepsClientSide(stepsNumber: number, autoCloseWebview?: boolean): void;
22
+ goBackAFewSteps(stepsNumber: number, autoCloseWebview?: boolean): void;
22
23
  navigateClientSide(url: HistoryPushStateParams[2], state?: HistoryPushStateParams[0], nativeTitle?: string): void;
23
24
  navigateServerSide(link: LocationAssignParam, nativeTitle?: string): void;
24
- reload(skipReload?: boolean): void;
25
+ replaceHistoryState(url?: HistoryPushStateParams[2], state?: HistoryPushStateParams[0]): void;
25
26
  setInitialView(nativeTitle?: string): void;
26
27
  setTitle(nativeTitle: string): void;
27
- replaceHistoryState(url?: HistoryPushStateParams[2], state?: HistoryPushStateParams[0]): void;
28
- private createStateWithPageId;
29
- private setHistoryStatePageId;
30
28
  /**
31
29
  * Метод, вычисляющий `pageId`, который нужно послать в NA
32
30
  * для правильной синхронизации с кнопкой "Назад". Также вычисляет `pageTitle`
@@ -41,19 +39,10 @@ export declare class NativeNavigationAndTitleService {
41
39
  */
42
40
  private handleClientSideNavigationBack;
43
41
  private hasSavedHistoryStack;
44
- /**
45
- * Инициализирует `nativeHistoryStack`.
46
- *
47
- * Подробное описание каждого сценария см. в {@link ./NAVIGATION_SCENARIOS.md}.
48
- */
49
42
  private initializeNativeHistoryStack;
43
+ private initializeForBackward;
50
44
  private initializeForNewOrigin;
51
45
  private initializeForForward;
52
- /**
53
- * Читает и парсит `nativeHistoryStack` из SessionStorage.
54
- * При ошибке чтения или парсинга логирует через `logError` и пробрасывает исключение.
55
- */
56
- private readSavedHistoryStack;
57
46
  /**
58
47
  * Подготавливает ссылку для корректного перехода server-side навигацией.
59
48
  *
@@ -63,12 +52,16 @@ export declare class NativeNavigationAndTitleService {
63
52
  */
64
53
  private prepareExternalLinkBeforeOpen;
65
54
  /**
66
- * Сохраняет `nativeHistoryStack` в SessionStorage.
55
+ * Читает и парсит `nativeHistoryStack` из SessionStorage.
56
+ * При ошибке чтения или парсинга логирует через `logError` и пробрасывает исключение.
67
57
  */
58
+ private readSavedHistoryStack;
68
59
  private saveNativeHistoryStack;
69
60
  /**
70
61
  * Синхронизирует состояние истории переходов и заголовок с NA в соответствии
71
62
  * с `nativeHistoryStack`.
72
63
  */
73
64
  private syncHistoryWithNative;
65
+ private setHistoryStatePageId;
66
+ private createStateWithPageId;
74
67
  }
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
- /* eslint max-lines: ["error", {"max": 400, "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");
@@ -20,13 +20,18 @@ class NativeNavigationAndTitleService {
20
20
  // Здесь сохраняются параметры, которые в последний раз были отправлены
21
21
  // в NA. Помогает предотвратить повторную отправку одинаковых параметров.
22
22
  this.lastSetPageSettingsParams = '';
23
+ // Поле для фолбэк сценария, чтобы не ломать обратную совместимость в версии `1.4.0`.
24
+ // Фолбэк сцерий используется для потребителей B2N, которые используют
25
+ // прямые вызовы `history.replaceState` (или обертки над ним, типа `history.replace` из ReactRouter)
26
+ // вместо нового метода B2N `replaceHistoryState`
27
+ this.numOfBackSteps = 1;
23
28
  // Предотвращают повторный вызов навигации, пока текущая не завершена.
24
29
  // Без блокировки WV-браузер продолжит показывать исходную страницу,
25
30
  // и повторный вызов может инициировать нежелательную навигацию.
26
31
  // `isGoBackLocked` снимается в `handleClientSideNavigationBack` (popstate) для soft-навигации;
27
32
  // при hard-навигации блокировка снимется автоматически при новой инициализации.
28
- // `isNavigateServerSideLocked` не снимается — после server-side навигации всегда новая инициализация.
29
33
  this.isGoBackLocked = false;
34
+ // `isNavigateServerSideLocked` не снимается — после server-side навигации всегда новая инициализация.
30
35
  this.isNavigateServerSideLocked = false;
31
36
  this.handleClientSideNavigationBack = this.handleClientSideNavigationBack.bind(this);
32
37
  window.addEventListener('popstate', this.handleClientSideNavigationBack); // без отписки т.к. Сервис используется в течение всей жизни WA
@@ -41,9 +46,9 @@ class NativeNavigationAndTitleService {
41
46
  return;
42
47
  }
43
48
  this.isGoBackLocked = true;
44
- this.goBackAFewStepsClientSide(-1, true);
49
+ this.goBackAFewSteps(-1, true);
45
50
  }
46
- goBackAFewStepsClientSide(stepsNumber, autoCloseWebview = false) {
51
+ goBackAFewSteps(stepsNumber, autoCloseWebview = false) {
47
52
  var _a;
48
53
  if (!stepsNumber) {
49
54
  return;
@@ -55,8 +60,12 @@ class NativeNavigationAndTitleService {
55
60
  (0, close_webview_util_1.closeWebviewUtil)();
56
61
  return;
57
62
  }
63
+ this.numOfBackSteps = maxStepsToBack;
64
+ }
65
+ else {
66
+ this.numOfBackSteps = stepsToBack;
58
67
  }
59
- const steps = -Math.min(stepsToBack, maxStepsToBack);
68
+ const steps = -this.numOfBackSteps;
60
69
  if ((_a = this.browserHistoryApiWrappers) === null || _a === void 0 ? void 0 : _a.go) {
61
70
  this.browserHistoryApiWrappers.go(steps);
62
71
  }
@@ -67,14 +76,13 @@ class NativeNavigationAndTitleService {
67
76
  }
68
77
  navigateClientSide(url, state = null, nativeTitle = '') {
69
78
  var _a;
70
- const nextPageId = this.nativeHistoryStack.length + 1;
71
- const stateWithPageId = this.createStateWithPageId(state, nextPageId);
72
79
  if ((_a = this.browserHistoryApiWrappers) === null || _a === void 0 ? void 0 : _a.push) {
73
- this.browserHistoryApiWrappers.push(url, stateWithPageId);
80
+ this.browserHistoryApiWrappers.push(url, state);
74
81
  }
75
82
  else {
76
- window.history.pushState(stateWithPageId, '', url);
83
+ window.history.pushState(state, '', url);
77
84
  }
85
+ this.setHistoryStatePageId(true);
78
86
  this.nativeHistoryStack.push(nativeTitle);
79
87
  this.saveNativeHistoryStack();
80
88
  this.syncHistoryWithNative();
@@ -92,12 +100,15 @@ class NativeNavigationAndTitleService {
92
100
  this.saveNativeHistoryStack();
93
101
  window.location.assign(this.prepareExternalLinkBeforeOpen(url));
94
102
  }
95
- // eslint-disable-next-line class-methods-use-this -- удобней использовать метод в контексте экземпляра.
96
- reload(skipReload = false) {
97
- document.cookie = `${query_and_headers_keys_1.COOKIE_KEY_BRIDGE_TO_NATIVE_RELOAD}=true; Path=/`;
98
- if (!skipReload) {
99
- 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);
100
107
  }
108
+ else {
109
+ window.history.replaceState(state, '', url);
110
+ }
111
+ this.setHistoryStatePageId();
101
112
  }
102
113
  setInitialView(nativeTitle = '') {
103
114
  this.nativeHistoryStack = [nativeTitle];
@@ -109,33 +120,6 @@ class NativeNavigationAndTitleService {
109
120
  this.saveNativeHistoryStack();
110
121
  this.syncHistoryWithNative();
111
122
  }
112
- replaceHistoryState(url, state = null) {
113
- var _a;
114
- const pageId = this.nativeHistoryStack.length;
115
- const stateWithPageId = this.createStateWithPageId(state, pageId);
116
- if ((_a = this.browserHistoryApiWrappers) === null || _a === void 0 ? void 0 : _a.replace) {
117
- this.browserHistoryApiWrappers.replace(url, stateWithPageId);
118
- }
119
- else {
120
- window.history.replaceState(stateWithPageId, '', url);
121
- }
122
- }
123
- // eslint-disable-next-line class-methods-use-this -- удобней использовать метод в контексте экземпляра.
124
- createStateWithPageId(state, pageId) {
125
- const isPlainObject = (v) => v !== null && v !== undefined && typeof v === 'object' && !Array.isArray(v);
126
- return Object.assign(Object.assign({}, (isPlainObject(state) ? state : {})), { [query_and_headers_keys_1.HISTORY_STATE_KEY_B2N_PAGE_ID]: pageId });
127
- }
128
- setHistoryStatePageId() {
129
- var _a;
130
- const pageId = this.nativeHistoryStack.length;
131
- const newState = this.createStateWithPageId(window.history.state, pageId);
132
- if ((_a = this.browserHistoryApiWrappers) === null || _a === void 0 ? void 0 : _a.replace) {
133
- this.browserHistoryApiWrappers.replace(undefined, newState);
134
- }
135
- else {
136
- window.history.replaceState(newState, '');
137
- }
138
- }
139
123
  /**
140
124
  * Метод, вычисляющий `pageId`, который нужно послать в NA
141
125
  * для правильной синхронизации с кнопкой "Назад". Также вычисляет `pageTitle`
@@ -166,8 +150,9 @@ class NativeNavigationAndTitleService {
166
150
  this.nativeHistoryStack = this.nativeHistoryStack.slice(0, statePageId);
167
151
  }
168
152
  else {
169
- this.nativeHistoryStack = this.nativeHistoryStack.slice(0, -1);
153
+ this.nativeHistoryStack = this.nativeHistoryStack.slice(0, -this.numOfBackSteps);
170
154
  }
155
+ this.numOfBackSteps = 1;
171
156
  if (this.nativeHistoryStack.length < 1) {
172
157
  (0, close_webview_util_1.closeWebviewUtil)();
173
158
  return;
@@ -179,11 +164,6 @@ class NativeNavigationAndTitleService {
179
164
  hasSavedHistoryStack() {
180
165
  return sessionStorage.getItem(query_and_headers_keys_1.SS_KEY_BRIDGE_TO_NATIVE_HISTORY_STACK) !== null;
181
166
  }
182
- /**
183
- * Инициализирует `nativeHistoryStack`.
184
- *
185
- * Подробное описание каждого сценария см. в {@link ./NAVIGATION_SCENARIOS.md}.
186
- */
187
167
  initializeNativeHistoryStack() {
188
168
  var _a;
189
169
  const { nextPageId, title } = this.nativeParamsService;
@@ -191,15 +171,7 @@ class NativeNavigationAndTitleService {
191
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];
192
172
  try {
193
173
  if (typeof statePageId === 'number') {
194
- const pageId = statePageId;
195
- if (hasSS) {
196
- const savedStack = this.readSavedHistoryStack();
197
- this.nativeHistoryStack = savedStack.slice(0, pageId);
198
- this.nativeHistoryStack[this.nativeHistoryStack.length - 1] = title;
199
- }
200
- else {
201
- this.nativeHistoryStack = [title];
202
- }
174
+ this.nativeHistoryStack = this.initializeForBackward(statePageId, title);
203
175
  }
204
176
  else if (nextPageId && !hasSS) {
205
177
  this.nativeHistoryStack = this.initializeForNewOrigin(nextPageId, title);
@@ -218,6 +190,15 @@ class NativeNavigationAndTitleService {
218
190
  this.syncHistoryWithNative();
219
191
  this.setHistoryStatePageId();
220
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
+ }
221
202
  // eslint-disable-next-line class-methods-use-this -- удобней использовать метод в контексте экземпляра.
222
203
  initializeForNewOrigin(nextPageId, title) {
223
204
  const stack = new Array(nextPageId).fill(NativeHistoryStackStub);
@@ -237,26 +218,6 @@ class NativeNavigationAndTitleService {
237
218
  stack[stack.length - 1] = title;
238
219
  return stack;
239
220
  }
240
- /**
241
- * Читает и парсит `nativeHistoryStack` из SessionStorage.
242
- * При ошибке чтения или парсинга логирует через `logError` и пробрасывает исключение.
243
- */
244
- readSavedHistoryStack() {
245
- const serialized = sessionStorage.getItem(query_and_headers_keys_1.SS_KEY_BRIDGE_TO_NATIVE_HISTORY_STACK);
246
- try {
247
- if (!serialized) {
248
- throw new Error(`${query_and_headers_keys_1.SS_KEY_BRIDGE_TO_NATIVE_HISTORY_STACK} sessionStorage expected not to be null`);
249
- }
250
- return JSON.parse(serialized);
251
- }
252
- catch (e) {
253
- if (this.logError) {
254
- this.logError(`Клиентский код B2N не смог получить ${query_and_headers_keys_1.SS_KEY_BRIDGE_TO_NATIVE_HISTORY_STACK} из sessionStorage
255
- Могут возникнуть проблемы с кнопкой «Назад» в NA.`, e);
256
- }
257
- throw e;
258
- }
259
- }
260
221
  /**
261
222
  * Подготавливает ссылку для корректного перехода server-side навигацией.
262
223
  *
@@ -282,8 +243,25 @@ class NativeNavigationAndTitleService {
282
243
  return modifiedUrl;
283
244
  }
284
245
  /**
285
- * Сохраняет `nativeHistoryStack` в SessionStorage.
246
+ * Читает и парсит `nativeHistoryStack` из SessionStorage.
247
+ * При ошибке чтения или парсинга логирует через `logError` и пробрасывает исключение.
286
248
  */
249
+ readSavedHistoryStack() {
250
+ const serialized = sessionStorage.getItem(query_and_headers_keys_1.SS_KEY_BRIDGE_TO_NATIVE_HISTORY_STACK);
251
+ try {
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`);
254
+ }
255
+ return JSON.parse(serialized);
256
+ }
257
+ catch (e) {
258
+ if (this.logError) {
259
+ this.logError(`Клиентский код B2N не смог получить ${query_and_headers_keys_1.SS_KEY_BRIDGE_TO_NATIVE_HISTORY_STACK} из sessionStorage
260
+ Могут возникнуть проблемы с кнопкой «Назад» в NA.`, e);
261
+ }
262
+ throw e;
263
+ }
264
+ }
287
265
  saveNativeHistoryStack() {
288
266
  const serializedNativeHistoryStack = JSON.stringify(this.nativeHistoryStack);
289
267
  sessionStorage.setItem(query_and_headers_keys_1.SS_KEY_BRIDGE_TO_NATIVE_HISTORY_STACK, serializedNativeHistoryStack);
@@ -313,5 +291,20 @@ class NativeNavigationAndTitleService {
313
291
  }
314
292
  }
315
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
+ }
316
309
  }
317
310
  exports.NativeNavigationAndTitleService = NativeNavigationAndTitleService;
package/package.json CHANGED
@@ -1,121 +1,121 @@
1
1
  {
2
- "name": "@alfalab/bridge-to-native",
3
- "version": "1.3.2-beta.b39ff6d",
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"
2
+ "name": "@alfalab/bridge-to-native",
3
+ "version": "1.4.0",
4
+ "license": "MIT",
5
+ "description": "Утилита для удобной работы веб приложения внутри нативного приложения и коммуникации с ним.",
6
+ "engines": {
7
+ "node": ">=20.19.2",
8
+ "npm": "please-use-yarn"
19
9
  },
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
- ]
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/core-ds/bridge-to-native.git"
68
13
  },
69
- "overrides": [
70
- {
71
- "files": [
72
- "__tests__/**/*"
73
- ],
74
- "rules": {
75
- "max-lines": "off"
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"
76
24
  }
77
- }
78
- ]
79
- },
80
- "jest": {
81
- "projects": [
82
- {
83
- "displayName": "client-tests",
84
- "testEnvironment": "@happy-dom/jest-environment",
85
- "testPathIgnorePatterns": [
86
- "/__tests__/server/"
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"
99
+ ],
100
+ "coveragePathIgnorePatterns": [
101
+ "/node_modules/",
102
+ "/src/(client|server)/index.ts"
103
+ ],
104
+ "reporters": [
105
+ "default",
106
+ "jest-junit"
87
107
  ]
88
- },
89
- {
90
- "displayName": "server-tests",
91
- "testEnvironment": "node",
92
- "testPathIgnorePatterns": [
93
- "/__tests__/client/"
108
+ },
109
+ "prettier": "arui-presets-lint/prettier",
110
+ "stylelint": {
111
+ "extends": "arui-presets-lint/stylelint",
112
+ "ignoreFiles": [
113
+ "coverage/**/*.js",
114
+ ".yarn/releases/*"
94
115
  ]
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"
116
+ },
117
+ "publishConfig": {
118
+ "registry": "https://registry.npmjs.org"
119
+ },
120
+ "packageManager": "yarn@4.12.0"
121
121
  }
@@ -1,7 +1,6 @@
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";
7
6
  export declare const HISTORY_STATE_KEY_B2N_PAGE_ID = "b2n-pageId";
@@ -3,7 +3,7 @@
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.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;
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
  /*
@@ -11,26 +11,25 @@ exports.HEADER_KEY_USER_AGENT = 'user-agent';
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';
26
- // Ключ в history.state, хранящий текущий pageId для корректной back-навигации.
22
+ // Ключ в history.state, который B2N использует для хранения текущего `pageId`
23
+ // чтобы иметь возможность корректно синхронизироваться с NA после back-навигации.
27
24
  exports.HISTORY_STATE_KEY_B2N_PAGE_ID = 'b2n-pageId';
28
25
  // Флаг, предписывающий клиентскому коду B2N начать связь
29
26
  // с нативным приложением с указанного в этом параметре id, а не с 1.
30
- // Необходимо для server-side навигации.
27
+ // Необходимо для server-side (hard) навигации.
31
28
  exports.QUERY_B2N_NEXT_PAGEID = 'b2n-next-page-id';
32
- // Query-параметр, с помощью которого можно передать начальное значение
29
+ // Query-параметр, который можно подмешать к URL в диплинке на открытие WV,
30
+ // и с помощью которого можно передать начальное значение
33
31
  // для нативного заголовка — в область, которую не рендерит веб.
32
+ // Также некоторые методы навигации B2N используют эту сущность.
34
33
  exports.QUERY_B2N_TITLE = 'b2n-title';
35
34
  // Deprecated вариант query-параметра 'b2n-title'
36
35
  // (т.к. `title` часто может использоваться самим веб-приложением).
@@ -38,18 +37,18 @@ exports.QUERY_B2N_TITLE = 'b2n-title';
38
37
  exports.QUERY_B2N_TITLE_DEPRECATED = 'title';
39
38
  // NA на iOS передаёт в этом параметре схему,
40
39
  // под которым оно зарегистрировано в OS.
41
- // Известные проблемы:
42
- // * В старых версиях отсутствует, клиентский код использует хардкод (см. `src/client/constants.ts`);
43
- // * В тестовых сборках часто нарушена связь!!! Между фактической схемой
44
- // и схемой, которая приходит в этом параметре.
45
- // Нужно просить iOS разработчика собрать сборку нормально.
40
+ // Некоторые особенности:
41
+ // * В старых версиях отсутствует, клиентский код использует хардкод
42
+ // (см. `VERSION_TO_IOS_APP_ID` в `src/client/constants.ts`);
43
+ // * Тестить WV на iOS надо на группе «B»!!! Иначе NA будет присылать в WA неверную схему,
44
+ // что приведёт к неработоспособности методов, связанных с передачей диплинков в NA.
45
+ // * В сборках для Симулятора, схема всегда `aweassist`.
46
46
  exports.QUERY_NATIVE_IOS_APPID = 'applicationId';
47
- // Исторически NA на iOS передаёт в этом параметре свою версию.
48
- // B2N сознательно переиспользует этот query и для server-side переходов в Android,
47
+ // NA на iOS передаёт в этом параметре свою версию.
48
+ // B2N сознательно переиспользует этот query и для server-side (hard) переходов в Android,
49
49
  // чтобы не вводить отдельный служебный параметр только для переноса версии между WA.
50
50
  exports.QUERY_NATIVE_IOS_APPVERSION = 'device_app_version';
51
51
  // NA на обеих платформах в этом параметре передаёт активную тему (светлая/тёмная).
52
52
  exports.QUERY_NATIVE_THEME = 'theme';
53
- // Ключ sesseionStorage, с помощью которого клиентская часть B2N сохраняет своё состояние
54
- // при server-side навигации.
53
+ // Ключ sessionStorage,в котором клиентская часть B2N сохраняет состояние синхронизации с NA.
55
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=/`);