@alfalab/bridge-to-native 0.2.2 → 1.0.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.
- package/client/bridge-to-native.d.ts +220 -0
- package/client/bridge-to-native.js +267 -0
- package/client/constants.d.ts +13 -0
- package/client/constants.js +30 -0
- package/client/services-and-utils/close-webview-util.d.ts +1 -0
- package/client/services-and-utils/close-webview-util.js +11 -0
- package/client/services-and-utils/external-links-service.d.ts +15 -0
- package/client/services-and-utils/external-links-service.js +71 -0
- package/client/services-and-utils/native-navigation-and-title-service.d.ts +70 -0
- package/client/services-and-utils/native-navigation-and-title-service.js +234 -0
- package/client/services-and-utils/native-params-service.d.ts +24 -0
- package/client/services-and-utils/native-params-service.js +79 -0
- package/client/types.d.ts +19 -0
- package/client/types.js +2 -0
- package/package.json +79 -79
- package/query-and-headers-keys.d.ts +11 -0
- package/query-and-headers-keys.js +45 -0
- package/server/extract-native-service-queries.d.ts +9 -0
- package/server/extract-native-service-queries.js +41 -0
- package/server/index.d.ts +2 -0
- package/server/index.js +7 -0
- package/server/is-webview-env.d.ts +7 -0
- package/server/is-webview-env.js +29 -0
- package/server/prepare-native-app-details-for-client.d.ts +13 -0
- package/server/prepare-native-app-details-for-client.js +78 -0
- package/server/regexp-patterns.d.ts +4 -0
- package/server/regexp-patterns.js +10 -0
- package/server/types.d.ts +2 -0
- package/server/types.js +2 -0
- package/server/utils.d.ts +24 -0
- package/server/utils.js +44 -0
- package/types.d.ts +1 -32
- package/bridge-to-native.d.ts +0 -63
- package/bridge-to-native.js +0 -147
- package/constants.d.ts +0 -15
- package/constants.js +0 -32
- package/esm/bridge-to-native.d.ts +0 -63
- package/esm/bridge-to-native.js +0 -154
- package/esm/constants.d.ts +0 -15
- package/esm/constants.js +0 -29
- package/esm/index.d.ts +0 -2
- package/esm/native-fallbacks.d.ts +0 -64
- package/esm/native-fallbacks.js +0 -119
- package/esm/native-navigation-and-title.d.ts +0 -147
- package/esm/native-navigation-and-title.js +0 -326
- package/esm/types.d.ts +0 -39
- package/esm/types.js +0 -1
- package/esm/utils.d.ts +0 -23
- package/esm/utils.js +0 -62
- package/index.d.ts +0 -2
- package/native-fallbacks.d.ts +0 -64
- package/native-fallbacks.js +0 -124
- package/native-navigation-and-title.d.ts +0 -147
- package/native-navigation-and-title.js +0 -329
- package/utils.d.ts +0 -23
- package/utils.js +0 -70
- /package/{esm/index.js → client/index.d.ts} +0 -0
- /package/{index.js → client/index.js} +0 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Ключи стандартных заголовков.
|
|
4
|
+
*/
|
|
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_NATIVE_APPVERSION = exports.COOKIE_KEY_BRIDGE_TO_NATIVE_DATA = exports.HEADER_KEY_USER_AGENT = exports.HEADER_KEY_COOKIE = void 0;
|
|
7
|
+
exports.HEADER_KEY_COOKIE = 'cookie';
|
|
8
|
+
exports.HEADER_KEY_USER_AGENT = 'user-agent';
|
|
9
|
+
/*
|
|
10
|
+
* Ключи заголовков и query-параметров, которые использует B2N.
|
|
11
|
+
*/
|
|
12
|
+
// Ключ cookie, с помощью которого серверная часть B2N передаст на клиент информацию об NA.
|
|
13
|
+
exports.COOKIE_KEY_BRIDGE_TO_NATIVE_DATA = 'bridgeToNativeData';
|
|
14
|
+
// Нативное приложение на обеих платформах подмешивает этот заголовок к запросу за HTML.
|
|
15
|
+
// TODO:
|
|
16
|
+
// * Исследовать, делает ли оно это при запросах к прочим ресурсам;
|
|
17
|
+
// * Есть подозрение на некоторую нестабильность — возможно делает это
|
|
18
|
+
// только для первого документа в вебвью-сессии. Исследовать.
|
|
19
|
+
exports.HEADER_KEY_NATIVE_APPVERSION = 'app-version';
|
|
20
|
+
// Флаг, предписывающий клиентскому коду B2N начать связь
|
|
21
|
+
// с нативным приложением с указанного в этом параметре id, а не с 1.
|
|
22
|
+
// Необходимо для server-side навигации.
|
|
23
|
+
exports.QUERY_B2N_NEXT_PAGEID = 'b2n-next-page-id';
|
|
24
|
+
// Query-параметр, с помощью которого можно передать начальное значение
|
|
25
|
+
// для нативного заголовка — в область, которую не рендерит веб.
|
|
26
|
+
exports.QUERY_B2N_TITLE = 'b2n-title';
|
|
27
|
+
// Deprecated вариант query-параметра 'b2n-title'
|
|
28
|
+
// (т.к. `title` часто может использоваться самим веб-приложением).
|
|
29
|
+
// Игнорируется, если указан 'b2n-title'.
|
|
30
|
+
exports.QUERY_B2N_TITLE_DEPRECATED = 'title';
|
|
31
|
+
// NA на iOS приложение передаёт в этом параметре схему,
|
|
32
|
+
// под которым оно зарегистрировано в OS.
|
|
33
|
+
// Известные проблемы:
|
|
34
|
+
// * В старых версиях отсутствует, клиентский код использует хардкод (см. `src/client/constants.ts`);
|
|
35
|
+
// * В тестовых сборках часто нарушена связь!!! Между фактической схемой
|
|
36
|
+
// и схемой, которая приходит в этом параметре.
|
|
37
|
+
// Нужно просить iOS разработчика собрать сборку нормально.
|
|
38
|
+
exports.QUERY_NATIVE_IOS_APPID = 'applicationId';
|
|
39
|
+
// NA на iOS приложение передаёт в этом параметре свою версию.
|
|
40
|
+
exports.QUERY_NATIVE_IOS_APPVERSION = 'device_app_version';
|
|
41
|
+
// NA на обеих платформах в этом параметре передаёт активную тему (светлая/тёмная).
|
|
42
|
+
exports.QUERY_NATIVE_THEME = 'theme';
|
|
43
|
+
// Ключ sesseionStorage, с помощью которого клиентская часть B2N сохраняет своё состояние
|
|
44
|
+
// при server-side навигации.
|
|
45
|
+
exports.SS_KEY_BRIDGE_TO_NATIVE_HISTORY_STACK = 'bridgeToNativeHistoryStack';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type UniversalRequest } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Извлекает из запроса все сервисные query-параметры, которые нативное приложение подмешивает в URL.
|
|
4
|
+
* И возвращает query-строку, состоящую только их них.
|
|
5
|
+
*
|
|
6
|
+
* @param request Объект запроса (Request или IncomingMessage).
|
|
7
|
+
* @return Строка, состоящая только из найденных сервисных query-параметров в формате: "title=Title&theme=dark...".
|
|
8
|
+
*/
|
|
9
|
+
export declare function extractNativeServiceQueries(request: UniversalRequest): string;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.extractNativeServiceQueries = extractNativeServiceQueries;
|
|
4
|
+
const utils_1 = require("./utils");
|
|
5
|
+
// Словарь всех известных на данный момент сервисных query-параметров,
|
|
6
|
+
// которые нативное приложение подмешивает в URL.
|
|
7
|
+
const nativeServiceQueryKeys = [
|
|
8
|
+
'applicationId',
|
|
9
|
+
'client_id',
|
|
10
|
+
'device_app_id',
|
|
11
|
+
'device_app_version',
|
|
12
|
+
'device_boot_time',
|
|
13
|
+
'device_id',
|
|
14
|
+
'device_locale',
|
|
15
|
+
'device_model',
|
|
16
|
+
'device_name',
|
|
17
|
+
'device_os_version',
|
|
18
|
+
'device_timezone',
|
|
19
|
+
'device_uuid',
|
|
20
|
+
'paySupported',
|
|
21
|
+
'scope',
|
|
22
|
+
'theme',
|
|
23
|
+
];
|
|
24
|
+
/**
|
|
25
|
+
* Извлекает из запроса все сервисные query-параметры, которые нативное приложение подмешивает в URL.
|
|
26
|
+
* И возвращает query-строку, состоящую только их них.
|
|
27
|
+
*
|
|
28
|
+
* @param request Объект запроса (Request или IncomingMessage).
|
|
29
|
+
* @return Строка, состоящая только из найденных сервисных query-параметров в формате: "title=Title&theme=dark...".
|
|
30
|
+
*/
|
|
31
|
+
function extractNativeServiceQueries(request) {
|
|
32
|
+
const serviceQueryValues = (0, utils_1.getQueryValues)(request, nativeServiceQueryKeys);
|
|
33
|
+
const foundQueries = new URLSearchParams();
|
|
34
|
+
serviceQueryValues.forEach((value, i) => {
|
|
35
|
+
const key = nativeServiceQueryKeys[i];
|
|
36
|
+
if (value) {
|
|
37
|
+
foundQueries.set(key, value);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
return foundQueries.toString();
|
|
41
|
+
}
|
package/server/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.prepareNativeAppDetailsForClient = exports.isWebviewEnv = void 0;
|
|
4
|
+
var is_webview_env_1 = require("./is-webview-env");
|
|
5
|
+
Object.defineProperty(exports, "isWebviewEnv", { enumerable: true, get: function () { return is_webview_env_1.isWebviewEnv; } });
|
|
6
|
+
var prepare_native_app_details_for_client_1 = require("./prepare-native-app-details-for-client");
|
|
7
|
+
Object.defineProperty(exports, "prepareNativeAppDetailsForClient", { enumerable: true, get: function () { return prepare_native_app_details_for_client_1.prepareNativeAppDetailsForClient; } });
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type UniversalRequest } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* На основе объекта запроса любого типа определяет, сделан ли запрос из вебвью.
|
|
4
|
+
*
|
|
5
|
+
* @param request Объект запроса (Request или IncomingMessage).
|
|
6
|
+
*/
|
|
7
|
+
export declare function isWebviewEnv(request: UniversalRequest): boolean;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isWebviewEnv = isWebviewEnv;
|
|
4
|
+
const query_and_headers_keys_1 = require("../query-and-headers-keys");
|
|
5
|
+
const regexp_patterns_1 = require("./regexp-patterns");
|
|
6
|
+
const utils_1 = require("./utils");
|
|
7
|
+
/**
|
|
8
|
+
* На основе объекта запроса любого типа определяет, сделан ли запрос из вебвью.
|
|
9
|
+
*
|
|
10
|
+
* @param request Объект запроса (Request или IncomingMessage).
|
|
11
|
+
*/
|
|
12
|
+
function isWebviewEnv(request) {
|
|
13
|
+
// Выставленная ранее кука — однозначный индикатор вебвью окружения.
|
|
14
|
+
if ((0, utils_1.hasBridgeToNativeDataCookie)(request)) {
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
const appVersion = (0, utils_1.getHeaderValue)(request, query_and_headers_keys_1.HEADER_KEY_NATIVE_APPVERSION);
|
|
18
|
+
// `app-version` в заголовках — основной индикатор запроса из вебвью.
|
|
19
|
+
if (appVersion && regexp_patterns_1.versionPattern.test(appVersion)) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
const userAgent = (0, utils_1.getHeaderValue)(request, query_and_headers_keys_1.HEADER_KEY_USER_AGENT);
|
|
23
|
+
// Проверка «на всякий случай» для iOS — нет уверенности,
|
|
24
|
+
// что `app-version` стабильно и на всех версиях есть во всех запросах из вебвью.
|
|
25
|
+
if (userAgent && regexp_patterns_1.webviewUaIOSPattern.test(userAgent)) {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type UniversalRequest } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Парсит запрос, доставая из него данные о нативном приложении,
|
|
4
|
+
* которое подмешивает их туда.
|
|
5
|
+
* Собранные данные о нативном приложении помещаются в не-HttpOnly куку
|
|
6
|
+
* для дальнейшего использования клиентским кодом bridge2native.
|
|
7
|
+
*
|
|
8
|
+
* @param request Объект запроса (Request или IncomingMessage).
|
|
9
|
+
* @param setResponseHeader Функция для добавления заголовка ответа.
|
|
10
|
+
* Нужно передать функцию, которая средствами используемого веб-сервера добавит заголовок в ответ.
|
|
11
|
+
* b2native с её помощью добавит `Set-Cookie` заголовок с некоторыми данными для своего клиентского кода.
|
|
12
|
+
*/
|
|
13
|
+
export declare function prepareNativeAppDetailsForClient(request: UniversalRequest, setResponseHeader: (headerKey: string, headerValue: string) => void): void;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.prepareNativeAppDetailsForClient = prepareNativeAppDetailsForClient;
|
|
4
|
+
const query_and_headers_keys_1 = require("../query-and-headers-keys");
|
|
5
|
+
const extract_native_service_queries_1 = require("./extract-native-service-queries");
|
|
6
|
+
const regexp_patterns_1 = require("./regexp-patterns");
|
|
7
|
+
const utils_1 = require("./utils");
|
|
8
|
+
/**
|
|
9
|
+
* Парсит запрос, доставая из него данные о нативном приложении,
|
|
10
|
+
* которое подмешивает их туда.
|
|
11
|
+
* Собранные данные о нативном приложении помещаются в не-HttpOnly куку
|
|
12
|
+
* для дальнейшего использования клиентским кодом bridge2native.
|
|
13
|
+
*
|
|
14
|
+
* @param request Объект запроса (Request или IncomingMessage).
|
|
15
|
+
* @param setResponseHeader Функция для добавления заголовка ответа.
|
|
16
|
+
* Нужно передать функцию, которая средствами используемого веб-сервера добавит заголовок в ответ.
|
|
17
|
+
* b2native с её помощью добавит `Set-Cookie` заголовок с некоторыми данными для своего клиентского кода.
|
|
18
|
+
*/
|
|
19
|
+
function prepareNativeAppDetailsForClient(request, setResponseHeader) {
|
|
20
|
+
// Если кука с данными о нативном приложении уже есть, информация уже собрана,
|
|
21
|
+
// делать больше ничего не нужно.
|
|
22
|
+
// Возможна смена темы нативного приложения (светлая/тёмная) во время вебвью-сессии.
|
|
23
|
+
// Но веб об этом не узнает, т.к. нативное приложение сообщает об этом
|
|
24
|
+
// только при старте нового вебвью.
|
|
25
|
+
if ((0, utils_1.hasBridgeToNativeDataCookie)(request)) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const nativeParams = parseRequest(request);
|
|
29
|
+
const serializedNativeParams = encodeURIComponent(JSON.stringify(nativeParams));
|
|
30
|
+
setResponseHeader('Set-Cookie', `${query_and_headers_keys_1.COOKIE_KEY_BRIDGE_TO_NATIVE_DATA}=${serializedNativeParams}`);
|
|
31
|
+
}
|
|
32
|
+
function parseRequest(request) {
|
|
33
|
+
// Прихраним «сервисные» query-параметры от нативного приложения,
|
|
34
|
+
// чтобы подмешать их к URL при переходе в другое веб-приложение
|
|
35
|
+
// в рамках одной вебвью-сессии.
|
|
36
|
+
const originalWebviewParams = (0, extract_native_service_queries_1.extractNativeServiceQueries)(request);
|
|
37
|
+
// Чтобы понять, как парсится запрос, см. описания констант в `src/server/constants.ts`
|
|
38
|
+
const [nextPageId, iosAppIdQuery, iosAppVersionQuery, theme, title, deprecatedTitle] = (0, utils_1.getQueryValues)(request, [
|
|
39
|
+
query_and_headers_keys_1.QUERY_B2N_NEXT_PAGEID,
|
|
40
|
+
query_and_headers_keys_1.QUERY_NATIVE_IOS_APPID,
|
|
41
|
+
query_and_headers_keys_1.QUERY_NATIVE_IOS_APPVERSION,
|
|
42
|
+
query_and_headers_keys_1.QUERY_NATIVE_THEME,
|
|
43
|
+
query_and_headers_keys_1.QUERY_B2N_TITLE,
|
|
44
|
+
query_and_headers_keys_1.QUERY_B2N_TITLE_DEPRECATED,
|
|
45
|
+
]);
|
|
46
|
+
const appVersionFromHeaders = (0, utils_1.getHeaderValue)(request, query_and_headers_keys_1.HEADER_KEY_NATIVE_APPVERSION);
|
|
47
|
+
const nativeParams = {
|
|
48
|
+
originalWebviewParams,
|
|
49
|
+
theme: theme || 'light',
|
|
50
|
+
};
|
|
51
|
+
if (Number(nextPageId) > 1) {
|
|
52
|
+
nativeParams.nextPageId = Number(nextPageId);
|
|
53
|
+
}
|
|
54
|
+
if (iosAppIdQuery && regexp_patterns_1.iosAppIdPattern.test(iosAppIdQuery)) {
|
|
55
|
+
const [, appIdSubsting] = iosAppIdQuery.match(regexp_patterns_1.iosAppIdPattern); // кастинг ок — в условии блока регулярка проверена
|
|
56
|
+
nativeParams.iosAppId = appIdSubsting;
|
|
57
|
+
}
|
|
58
|
+
if (iosAppVersionQuery && regexp_patterns_1.versionPattern.test(iosAppVersionQuery)) {
|
|
59
|
+
nativeParams.appVersion = iosAppVersionQuery;
|
|
60
|
+
}
|
|
61
|
+
else if (appVersionFromHeaders && regexp_patterns_1.versionPattern.test(appVersionFromHeaders)) {
|
|
62
|
+
const [, versionSubstring] = appVersionFromHeaders.match(regexp_patterns_1.versionPattern); // кастинг ок — в условии блока регулярка проверена
|
|
63
|
+
nativeParams.appVersion = versionSubstring;
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
nativeParams.appVersion = '0.0.0';
|
|
67
|
+
}
|
|
68
|
+
// Здесь такая проверка сделана намеренно, чтобы можно было задать пустой заголовок с помощью пустой строки:
|
|
69
|
+
// http://example.com?b2n-title= → title === ''
|
|
70
|
+
// http://example.com → title === null
|
|
71
|
+
if (typeof title === 'string') {
|
|
72
|
+
nativeParams.title = title;
|
|
73
|
+
}
|
|
74
|
+
else if (typeof deprecatedTitle === 'string') {
|
|
75
|
+
nativeParams.title = deprecatedTitle;
|
|
76
|
+
}
|
|
77
|
+
return nativeParams;
|
|
78
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.webviewUaIOSPattern = exports.versionPattern = exports.iosAppIdPattern = exports.bridgeToNativeDataCookieExistencePattern = void 0;
|
|
4
|
+
const query_and_headers_keys_1 = require("../query-and-headers-keys");
|
|
5
|
+
// Проверка, согласно спецификации https://httpwg.org/specs/rfc6265.html#cookie
|
|
6
|
+
exports.bridgeToNativeDataCookieExistencePattern = new RegExp(`^(.+;\\s?)?${query_and_headers_keys_1.COOKIE_KEY_BRIDGE_TO_NATIVE_DATA}=`);
|
|
7
|
+
exports.iosAppIdPattern = /^com\.([a-z]+)\.app$/;
|
|
8
|
+
// Вебвью Android приписывает после версии тип билда, например `feature`. Нам эта информация не нужна.
|
|
9
|
+
exports.versionPattern = /^(\d+\.\d+\.\d+)(\s.+)?$/;
|
|
10
|
+
exports.webviewUaIOSPattern = /WebView|(iPhone|iPod|iPad)(?!.*Safari)/i;
|
package/server/types.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type UniversalRequest } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* На основе объекта запроса любого типа возвращает
|
|
4
|
+
* значение заголовка по заданному ключу.
|
|
5
|
+
*
|
|
6
|
+
* @param request Объект запроса (Request или IncomingMessage).
|
|
7
|
+
* @param headerKey Ключ заголовка, значение которого нужно получить.
|
|
8
|
+
*/
|
|
9
|
+
export declare function getHeaderValue(request: UniversalRequest, headerKey: string): string | null;
|
|
10
|
+
/**
|
|
11
|
+
* На основе объекта запроса любого типа возвращает
|
|
12
|
+
* значение query-параметра(ов) по ключу(ам).
|
|
13
|
+
*
|
|
14
|
+
* @param request Объект запроса (Request или IncomingMessage).
|
|
15
|
+
* @param queryKeys Ключ(и) query-параметра(ов), значение(я) которого(ых) нужно получить.
|
|
16
|
+
*/
|
|
17
|
+
export declare function getQueryValues(request: UniversalRequest, queryKeys: string): string | null;
|
|
18
|
+
export declare function getQueryValues(request: UniversalRequest, queryKeys: string[]): Array<string | null>;
|
|
19
|
+
/**
|
|
20
|
+
* Проверяет наличие в запросе bridgeToNativeData куки.
|
|
21
|
+
*
|
|
22
|
+
* @param request Объект запроса (Request или IncomingMessage).
|
|
23
|
+
*/
|
|
24
|
+
export declare function hasBridgeToNativeDataCookie(request: UniversalRequest): boolean;
|
package/server/utils.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getHeaderValue = getHeaderValue;
|
|
4
|
+
exports.getQueryValues = getQueryValues;
|
|
5
|
+
exports.hasBridgeToNativeDataCookie = hasBridgeToNativeDataCookie;
|
|
6
|
+
const query_and_headers_keys_1 = require("../query-and-headers-keys");
|
|
7
|
+
const regexp_patterns_1 = require("./regexp-patterns");
|
|
8
|
+
/**
|
|
9
|
+
* На основе объекта запроса любого типа возвращает
|
|
10
|
+
* значение заголовка по заданному ключу.
|
|
11
|
+
*
|
|
12
|
+
* @param request Объект запроса (Request или IncomingMessage).
|
|
13
|
+
* @param headerKey Ключ заголовка, значение которого нужно получить.
|
|
14
|
+
*/
|
|
15
|
+
function getHeaderValue(request, headerKey) {
|
|
16
|
+
if (request.headers instanceof Headers) {
|
|
17
|
+
return request.headers.get(headerKey);
|
|
18
|
+
}
|
|
19
|
+
const headerValue = request.headers[headerKey] || null;
|
|
20
|
+
// Массив создается только для множественных `set-cookie` заголовков,
|
|
21
|
+
// что не актуально в контексте запроса из браузера.
|
|
22
|
+
// https://nodejs.org/api/http.html#messageheaders
|
|
23
|
+
return Array.isArray(headerValue) ? headerValue[0] : headerValue;
|
|
24
|
+
}
|
|
25
|
+
function getQueryValues(request, queryKeys) {
|
|
26
|
+
if (!request.url || request.url.indexOf('?') === -1) {
|
|
27
|
+
return Array.isArray(queryKeys) ? queryKeys.map(() => null) : null;
|
|
28
|
+
}
|
|
29
|
+
const [, queryString] = request.url.split('?');
|
|
30
|
+
const searchParams = new URLSearchParams(queryString);
|
|
31
|
+
if (Array.isArray(queryKeys)) {
|
|
32
|
+
return queryKeys.map((key) => searchParams.get(key));
|
|
33
|
+
}
|
|
34
|
+
return searchParams.get(queryKeys);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Проверяет наличие в запросе bridgeToNativeData куки.
|
|
38
|
+
*
|
|
39
|
+
* @param request Объект запроса (Request или IncomingMessage).
|
|
40
|
+
*/
|
|
41
|
+
function hasBridgeToNativeDataCookie(request) {
|
|
42
|
+
const cookies = getHeaderValue(request, query_and_headers_keys_1.HEADER_KEY_COOKIE);
|
|
43
|
+
return Boolean(cookies && regexp_patterns_1.bridgeToNativeDataCookieExistencePattern.test(cookies));
|
|
44
|
+
}
|
package/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export
|
|
1
|
+
export type NativeParams = {
|
|
2
2
|
appVersion: string;
|
|
3
3
|
title?: string;
|
|
4
4
|
iosAppId?: string;
|
|
@@ -6,34 +6,3 @@ export declare type NativeParams = {
|
|
|
6
6
|
nextPageId: number | null;
|
|
7
7
|
originalWebviewParams: string;
|
|
8
8
|
};
|
|
9
|
-
export declare type NativeFeatureKey = 'geolocation' | 'linksInBrowser' | 'savedBackStack';
|
|
10
|
-
declare type NativeFeaturesParams = Readonly<Record<NativeFeatureKey, {
|
|
11
|
-
fromVersion: string;
|
|
12
|
-
}>>;
|
|
13
|
-
export declare type NativeFeaturesFromVersion = Readonly<{
|
|
14
|
-
android: NativeFeaturesParams;
|
|
15
|
-
ios: NativeFeaturesParams;
|
|
16
|
-
}>;
|
|
17
|
-
export declare type Environment = 'android' | 'ios';
|
|
18
|
-
export declare type WebViewWindow = Window & {
|
|
19
|
-
Android?: {
|
|
20
|
-
setPageSettings: (params: string) => void;
|
|
21
|
-
};
|
|
22
|
-
handleRedirect?: (appName: string, path?: string, params?: Record<string, string>) => VoidFunction;
|
|
23
|
-
};
|
|
24
|
-
export declare type PdfType = 'pdfFile' | 'base64' | 'binary';
|
|
25
|
-
export declare type PreviousBridgeToNativeState = Omit<NativeParams, 'title' | 'theme'> & {
|
|
26
|
-
theme: 'dark' | 'light';
|
|
27
|
-
};
|
|
28
|
-
export declare type PreviousNativeNavigationAndTitleState = {
|
|
29
|
-
nativeHistoryStack: string[];
|
|
30
|
-
title: string;
|
|
31
|
-
};
|
|
32
|
-
export declare type SyncPurpose = 'initialization' | 'navigation' | 'title-replacing';
|
|
33
|
-
export declare type HandleRedirect = (appName: string, path?: string, params?: Record<string, string>, historyState?: Record<string, unknown>) => void;
|
|
34
|
-
export declare type Theme = 'light' | 'dark';
|
|
35
|
-
export declare type ExternalNavigationOptions = {
|
|
36
|
-
onClick?: () => void;
|
|
37
|
-
forceOpenInWebview?: boolean;
|
|
38
|
-
};
|
|
39
|
-
export {};
|
package/bridge-to-native.d.ts
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { NativeFallbacks } from './native-fallbacks';
|
|
2
|
-
import { NativeNavigationAndTitle } from './native-navigation-and-title';
|
|
3
|
-
import type { Environment, HandleRedirect, NativeFeatureKey, NativeParams, Theme } from './types';
|
|
4
|
-
/**
|
|
5
|
-
* Этот класс — абстракция для связи веб приложения с нативом и предназначен ТОЛЬКО
|
|
6
|
-
* для использования в вебвью окружении.
|
|
7
|
-
*/
|
|
8
|
-
export declare class BridgeToNative {
|
|
9
|
-
readonly AndroidBridge: {
|
|
10
|
-
setPageSettings: (params: string) => void;
|
|
11
|
-
} | undefined;
|
|
12
|
-
readonly environment: Environment;
|
|
13
|
-
readonly nativeFallbacks: NativeFallbacks;
|
|
14
|
-
private nextPageId;
|
|
15
|
-
private readonly _blankPagePath;
|
|
16
|
-
private readonly _handleRedirect;
|
|
17
|
-
constructor(handleRedirect: HandleRedirect, blankPagePath: string, nativeParams?: NativeParams);
|
|
18
|
-
private _nativeNavigationAndTitle;
|
|
19
|
-
get nativeNavigationAndTitle(): NativeNavigationAndTitle;
|
|
20
|
-
private _originalWebviewParams;
|
|
21
|
-
get originalWebviewParams(): string;
|
|
22
|
-
private _appVersion;
|
|
23
|
-
get appVersion(): string;
|
|
24
|
-
private _iosAppId?;
|
|
25
|
-
get iosAppId(): string | undefined;
|
|
26
|
-
private _theme;
|
|
27
|
-
get theme(): Theme;
|
|
28
|
-
/**
|
|
29
|
-
* Метод, проверяющий, можно ли использовать нативную функциональность в текущей версии приложения.
|
|
30
|
-
*
|
|
31
|
-
* @param feature Название функциональности, которую нужно проверить.
|
|
32
|
-
*/
|
|
33
|
-
canUseNativeFeature(feature: NativeFeatureKey): boolean;
|
|
34
|
-
/**
|
|
35
|
-
* Метод, отправляющий сигнал нативу, что нужно закрыть текущее вебвью.
|
|
36
|
-
*/
|
|
37
|
-
closeWebview(): void;
|
|
38
|
-
/**
|
|
39
|
-
* Сравнивает текущую версию приложения с переданной.
|
|
40
|
-
*
|
|
41
|
-
* @param versionToCompare Версия, с которой нужно сравнить текущую.
|
|
42
|
-
* @returns `true` – текущая версия больше или равняется переданной,
|
|
43
|
-
* `false` – текущая версия ниже.
|
|
44
|
-
*/
|
|
45
|
-
isCurrentVersionHigherOrEqual(versionToCompare: string): boolean;
|
|
46
|
-
checkAndroidAllowOpenInNewWebview(): boolean;
|
|
47
|
-
/**
|
|
48
|
-
* Сохраняет текущее состояние BridgeToNative в sessionStorage.
|
|
49
|
-
* Так же сохраняет текущее состояние nativeNavigationAndTitle.
|
|
50
|
-
*/
|
|
51
|
-
private saveCurrentState;
|
|
52
|
-
/**
|
|
53
|
-
* Возвращает схему приложения в iOS окружении, на основе версии.
|
|
54
|
-
*
|
|
55
|
-
* @param knownIosAppId Тип iOS приложения, если он известен.
|
|
56
|
-
* @returns Тип приложения, `undefined` для Android окружения.
|
|
57
|
-
*/
|
|
58
|
-
private getIosAppId;
|
|
59
|
-
/**
|
|
60
|
-
* Восстанавливает свое предыдущее состояние из sessionStorage
|
|
61
|
-
*/
|
|
62
|
-
private restorePreviousState;
|
|
63
|
-
}
|
package/bridge-to-native.js
DELETED
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/* eslint-disable no-underscore-dangle */
|
|
3
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.BridgeToNative = void 0;
|
|
5
|
-
const constants_1 = require("./constants");
|
|
6
|
-
const native_fallbacks_1 = require("./native-fallbacks");
|
|
7
|
-
const native_navigation_and_title_1 = require("./native-navigation-and-title");
|
|
8
|
-
const utils_1 = require("./utils");
|
|
9
|
-
/**
|
|
10
|
-
* Этот класс — абстракция для связи веб приложения с нативом и предназначен ТОЛЬКО
|
|
11
|
-
* для использования в вебвью окружении.
|
|
12
|
-
*/
|
|
13
|
-
class BridgeToNative {
|
|
14
|
-
constructor(handleRedirect, blankPagePath, nativeParams) {
|
|
15
|
-
// Webview, запущенное в Android окружении имеет объект `Android` в window.
|
|
16
|
-
this.AndroidBridge = window.Android;
|
|
17
|
-
this.environment = this.AndroidBridge ? 'android' : 'ios';
|
|
18
|
-
const previousState = !!sessionStorage.getItem(constants_1.PREVIOUS_B2N_STATE_STORAGE_KEY);
|
|
19
|
-
if (previousState) {
|
|
20
|
-
this._handleRedirect = handleRedirect;
|
|
21
|
-
this.restorePreviousState();
|
|
22
|
-
this.nativeFallbacks = new native_fallbacks_1.NativeFallbacks(this);
|
|
23
|
-
this._blankPagePath = blankPagePath;
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
this._appVersion =
|
|
27
|
-
nativeParams && (0, utils_1.isValidVersionFormat)(nativeParams === null || nativeParams === void 0 ? void 0 : nativeParams.appVersion)
|
|
28
|
-
? nativeParams.appVersion
|
|
29
|
-
: '0.0.0';
|
|
30
|
-
this._iosAppId = this.getIosAppId(nativeParams === null || nativeParams === void 0 ? void 0 : nativeParams.iosAppId);
|
|
31
|
-
this._theme = (nativeParams === null || nativeParams === void 0 ? void 0 : nativeParams.theme) === 'dark' ? 'dark' : 'light';
|
|
32
|
-
this._originalWebviewParams = (nativeParams === null || nativeParams === void 0 ? void 0 : nativeParams.originalWebviewParams) || '';
|
|
33
|
-
this._nativeNavigationAndTitle = new native_navigation_and_title_1.NativeNavigationAndTitle(this, nativeParams ? nativeParams.nextPageId : null, nativeParams === null || nativeParams === void 0 ? void 0 : nativeParams.title, handleRedirect);
|
|
34
|
-
this._handleRedirect = handleRedirect;
|
|
35
|
-
this.nextPageId = nativeParams ? nativeParams.nextPageId : null;
|
|
36
|
-
this.nativeFallbacks = new native_fallbacks_1.NativeFallbacks(this);
|
|
37
|
-
this._blankPagePath = blankPagePath;
|
|
38
|
-
}
|
|
39
|
-
get nativeNavigationAndTitle() {
|
|
40
|
-
return this._nativeNavigationAndTitle;
|
|
41
|
-
}
|
|
42
|
-
get originalWebviewParams() {
|
|
43
|
-
return this._originalWebviewParams;
|
|
44
|
-
}
|
|
45
|
-
get appVersion() {
|
|
46
|
-
return this._appVersion;
|
|
47
|
-
}
|
|
48
|
-
get iosAppId() {
|
|
49
|
-
return this._iosAppId;
|
|
50
|
-
}
|
|
51
|
-
get theme() {
|
|
52
|
-
return this._theme;
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Метод, проверяющий, можно ли использовать нативную функциональность в текущей версии приложения.
|
|
56
|
-
*
|
|
57
|
-
* @param feature Название функциональности, которую нужно проверить.
|
|
58
|
-
*/
|
|
59
|
-
canUseNativeFeature(feature) {
|
|
60
|
-
const { fromVersion } = constants_1.nativeFeaturesFromVersion[this.environment][feature];
|
|
61
|
-
return this.isCurrentVersionHigherOrEqual(fromVersion);
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Метод, отправляющий сигнал нативу, что нужно закрыть текущее вебвью.
|
|
65
|
-
*/
|
|
66
|
-
// eslint-disable-next-line class-methods-use-this
|
|
67
|
-
closeWebview() {
|
|
68
|
-
const originalPageUrl = new URL(window.location.href);
|
|
69
|
-
originalPageUrl.searchParams.set(constants_1.CLOSE_WEBVIEW_SEARCH_KEY, constants_1.CLOSE_WEBVIEW_SEARCH_VALUE);
|
|
70
|
-
window.location.href = originalPageUrl.toString();
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Сравнивает текущую версию приложения с переданной.
|
|
74
|
-
*
|
|
75
|
-
* @param versionToCompare Версия, с которой нужно сравнить текущую.
|
|
76
|
-
* @returns `true` – текущая версия больше или равняется переданной,
|
|
77
|
-
* `false` – текущая версия ниже.
|
|
78
|
-
*/
|
|
79
|
-
isCurrentVersionHigherOrEqual(versionToCompare) {
|
|
80
|
-
if (!(0, utils_1.isValidVersionFormat)(versionToCompare)) {
|
|
81
|
-
return false;
|
|
82
|
-
}
|
|
83
|
-
const matchPattern = /(\d+)\.(\d+)\.(\d+)/;
|
|
84
|
-
const [, ...appVersionComponents] = this._appVersion.match(matchPattern); // Формат версии проверен в конструкторе, можно смело убирать `null` из типа.
|
|
85
|
-
const [, ...versionToCompareComponents] = versionToCompare.match(matchPattern);
|
|
86
|
-
for (let i = 0; i < appVersionComponents.length; i++) {
|
|
87
|
-
if (appVersionComponents[i] !== versionToCompareComponents[i]) {
|
|
88
|
-
return appVersionComponents[i] >= versionToCompareComponents[i];
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
return true;
|
|
92
|
-
}
|
|
93
|
-
checkAndroidAllowOpenInNewWebview() {
|
|
94
|
-
const comparisonResult = this.isCurrentVersionHigherOrEqual(constants_1.START_VERSION_ANDROID_ALLOW_OPEN_NEW_WEBVIEW);
|
|
95
|
-
return this.environment === 'android' && comparisonResult;
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Сохраняет текущее состояние BridgeToNative в sessionStorage.
|
|
99
|
-
* Так же сохраняет текущее состояние nativeNavigationAndTitle.
|
|
100
|
-
*/
|
|
101
|
-
saveCurrentState() {
|
|
102
|
-
// В nativeNavigationAndTitle этот метод отмечен модификатором доступа private дабы не торчал наружу, но тут его нужно вызвать
|
|
103
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
104
|
-
// @ts-ignore
|
|
105
|
-
this._nativeNavigationAndTitle.saveCurrentState();
|
|
106
|
-
const currentState = {
|
|
107
|
-
appVersion: this._appVersion,
|
|
108
|
-
theme: this._theme,
|
|
109
|
-
nextPageId: this.nextPageId,
|
|
110
|
-
originalWebviewParams: this._originalWebviewParams || '',
|
|
111
|
-
iosAppId: this._iosAppId,
|
|
112
|
-
};
|
|
113
|
-
sessionStorage.setItem(constants_1.PREVIOUS_B2N_STATE_STORAGE_KEY, JSON.stringify(currentState));
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* Возвращает схему приложения в iOS окружении, на основе версии.
|
|
117
|
-
*
|
|
118
|
-
* @param knownIosAppId Тип iOS приложения, если он известен.
|
|
119
|
-
* @returns Тип приложения, `undefined` для Android окружения.
|
|
120
|
-
*/
|
|
121
|
-
getIosAppId(knownIosAppId) {
|
|
122
|
-
if (this.environment !== 'ios') {
|
|
123
|
-
return undefined;
|
|
124
|
-
}
|
|
125
|
-
if (knownIosAppId) {
|
|
126
|
-
return knownIosAppId;
|
|
127
|
-
}
|
|
128
|
-
const keys = Object.keys(constants_1.versionToIosAppId);
|
|
129
|
-
const rightKey = [...keys].reverse().find((version) => this.isCurrentVersionHigherOrEqual(version)) ||
|
|
130
|
-
keys[0];
|
|
131
|
-
return atob(constants_1.versionToIosAppId[rightKey]);
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* Восстанавливает свое предыдущее состояние из sessionStorage
|
|
135
|
-
*/
|
|
136
|
-
restorePreviousState() {
|
|
137
|
-
const previousState = JSON.parse(sessionStorage.getItem(constants_1.PREVIOUS_B2N_STATE_STORAGE_KEY) || '');
|
|
138
|
-
this._appVersion = previousState.appVersion;
|
|
139
|
-
this._iosAppId = previousState.iosAppId;
|
|
140
|
-
this._theme = previousState.theme;
|
|
141
|
-
this._originalWebviewParams = previousState.originalWebviewParams;
|
|
142
|
-
this.nextPageId = previousState.nextPageId;
|
|
143
|
-
this._nativeNavigationAndTitle = new native_navigation_and_title_1.NativeNavigationAndTitle(this, previousState.nextPageId, '', this._handleRedirect);
|
|
144
|
-
sessionStorage.removeItem(constants_1.PREVIOUS_B2N_STATE_STORAGE_KEY);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
exports.BridgeToNative = BridgeToNative;
|
package/constants.d.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { NativeFeaturesFromVersion } from './types';
|
|
2
|
-
export declare const START_VERSION_ANDROID_ALLOW_OPEN_NEW_WEBVIEW = "10.35.0";
|
|
3
|
-
export declare const ANDROID_APP_ID = "YWxmYWJhbms=";
|
|
4
|
-
export declare const CLOSE_WEBVIEW_SEARCH_KEY = "closeWebView";
|
|
5
|
-
export declare const CLOSE_WEBVIEW_SEARCH_VALUE = "true";
|
|
6
|
-
export declare const PREVIOUS_B2N_STATE_STORAGE_KEY = "previousBridgeToNativeState";
|
|
7
|
-
export declare const PREVIOUS_NATIVE_NAVIGATION_AND_TITLE_STATE_STORAGE_KEY = "previousNativeNavigationAndTitleState";
|
|
8
|
-
export declare const versionToIosAppId: {
|
|
9
|
-
readonly '0.0.0': "YWxmYWJhbms=";
|
|
10
|
-
readonly '12.22.0': "YWNvbmNpZXJnZQ==";
|
|
11
|
-
readonly '12.26.0': "a2l0dHljYXNo";
|
|
12
|
-
readonly '12.31.0': "YXdlYXNzaXN0";
|
|
13
|
-
};
|
|
14
|
-
export declare const nativeFeaturesFromVersion: NativeFeaturesFromVersion;
|
|
15
|
-
export declare const DEEP_LINK_PATTERN: RegExp;
|
package/constants.js
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DEEP_LINK_PATTERN = exports.nativeFeaturesFromVersion = exports.versionToIosAppId = exports.PREVIOUS_NATIVE_NAVIGATION_AND_TITLE_STATE_STORAGE_KEY = exports.PREVIOUS_B2N_STATE_STORAGE_KEY = exports.CLOSE_WEBVIEW_SEARCH_VALUE = exports.CLOSE_WEBVIEW_SEARCH_KEY = exports.ANDROID_APP_ID = exports.START_VERSION_ANDROID_ALLOW_OPEN_NEW_WEBVIEW = void 0;
|
|
4
|
-
exports.START_VERSION_ANDROID_ALLOW_OPEN_NEW_WEBVIEW = '10.35.0';
|
|
5
|
-
exports.ANDROID_APP_ID = 'YWxmYWJhbms=';
|
|
6
|
-
exports.CLOSE_WEBVIEW_SEARCH_KEY = 'closeWebView';
|
|
7
|
-
exports.CLOSE_WEBVIEW_SEARCH_VALUE = 'true';
|
|
8
|
-
exports.PREVIOUS_B2N_STATE_STORAGE_KEY = 'previousBridgeToNativeState';
|
|
9
|
-
exports.PREVIOUS_NATIVE_NAVIGATION_AND_TITLE_STATE_STORAGE_KEY = 'previousNativeNavigationAndTitleState';
|
|
10
|
-
exports.versionToIosAppId = {
|
|
11
|
-
'0.0.0': 'YWxmYWJhbms=',
|
|
12
|
-
'12.22.0': 'YWNvbmNpZXJnZQ==',
|
|
13
|
-
'12.26.0': 'a2l0dHljYXNo',
|
|
14
|
-
'12.31.0': 'YXdlYXNzaXN0',
|
|
15
|
-
};
|
|
16
|
-
exports.nativeFeaturesFromVersion = {
|
|
17
|
-
android: {
|
|
18
|
-
linksInBrowser: {
|
|
19
|
-
fromVersion: '11.71.0',
|
|
20
|
-
},
|
|
21
|
-
geolocation: { fromVersion: '11.71.0' },
|
|
22
|
-
savedBackStack: { fromVersion: '12.30.0' },
|
|
23
|
-
},
|
|
24
|
-
ios: {
|
|
25
|
-
linksInBrowser: {
|
|
26
|
-
fromVersion: '13.3.0',
|
|
27
|
-
},
|
|
28
|
-
geolocation: { fromVersion: '0.0.0' },
|
|
29
|
-
savedBackStack: { fromVersion: '0.0.0' }
|
|
30
|
-
},
|
|
31
|
-
};
|
|
32
|
-
exports.DEEP_LINK_PATTERN = /^(\/|\x61\x6c\x66\x61\x62\x61\x6e\x6b:\/{3}dashboard\/|\x61\x6c\x66\x61\x62\x61\x6e\x6b:\/{3}|\x61\x6c\x66\x61\x62\x61\x6e\x6b:\/{2}|https:\/{2}\x6f\x6e\x6c\x69\x6e\x65\x2e\x61\x6c\x66\x61\x62\x61\x6e\x6b\x2e\x72\x75\/)/;
|