@alfalab/bridge-to-native 1.1.0 → 1.2.1
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 +4 -0
- package/client/bridge-to-native.js +6 -0
- package/client/services-and-utils/native-params-service.d.ts +1 -0
- package/client/services-and-utils/native-params-service.js +1 -0
- package/package.json +1 -1
- package/query-and-headers-keys.d.ts +1 -0
- package/query-and-headers-keys.js +3 -1
- package/server/prepare-native-app-details-for-client.d.ts +1 -2
- package/server/prepare-native-app-details-for-client.js +21 -28
- package/server/utils.d.ts +12 -1
- package/server/utils.js +32 -10
- package/types.d.ts +1 -0
|
@@ -46,6 +46,10 @@ export declare class BridgeToNative {
|
|
|
46
46
|
* Тема (светлая/тёмная), в котором работает NA.
|
|
47
47
|
*/
|
|
48
48
|
get theme(): import("./types").Theme;
|
|
49
|
+
/**
|
|
50
|
+
* Время старта открытия WV экрана в формате timestamp. Значение никак не меняется в процессе всей жизни WV экрана.
|
|
51
|
+
*/
|
|
52
|
+
get webviewLaunchTime(): number | null;
|
|
49
53
|
/**
|
|
50
54
|
* Метод, проверяющий, можно ли использовать ли указанную функциональность
|
|
51
55
|
* в текущей версии NA.
|
|
@@ -62,6 +62,12 @@ class BridgeToNative {
|
|
|
62
62
|
get theme() {
|
|
63
63
|
return this.nativeParamsService.theme;
|
|
64
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Время старта открытия WV экрана в формате timestamp. Значение никак не меняется в процессе всей жизни WV экрана.
|
|
67
|
+
*/
|
|
68
|
+
get webviewLaunchTime() {
|
|
69
|
+
return this.nativeParamsService.webviewLaunchTime;
|
|
70
|
+
}
|
|
65
71
|
/**
|
|
66
72
|
* Метод, проверяющий, можно ли использовать ли указанную функциональность
|
|
67
73
|
* в текущей версии NA.
|
|
@@ -15,6 +15,7 @@ export declare class NativeParamsService {
|
|
|
15
15
|
originalWebviewParams: string | null;
|
|
16
16
|
theme: Theme;
|
|
17
17
|
title: string;
|
|
18
|
+
webviewLaunchTime: number | null;
|
|
18
19
|
constructor(logError?: LogError | undefined);
|
|
19
20
|
canUseNativeFeature(feature: NativeFeatureKey): boolean;
|
|
20
21
|
isCurrentVersionHigherOrEqual(versionToCompare: string): boolean;
|
|
@@ -21,6 +21,7 @@ class NativeParamsService {
|
|
|
21
21
|
this.originalWebviewParams = (nativeParams === null || nativeParams === void 0 ? void 0 : nativeParams.originalWebviewParams) || null;
|
|
22
22
|
this.theme = (nativeParams === null || nativeParams === void 0 ? void 0 : nativeParams.theme) === 'dark' ? 'dark' : 'light';
|
|
23
23
|
this.title = (nativeParams === null || nativeParams === void 0 ? void 0 : nativeParams.title) || '';
|
|
24
|
+
this.webviewLaunchTime = (nativeParams === null || nativeParams === void 0 ? void 0 : nativeParams.webviewLaunchTime) || null;
|
|
24
25
|
}
|
|
25
26
|
canUseNativeFeature(feature) {
|
|
26
27
|
const { fromVersion } = constants_1.NATIVE_FEATURES_FROM_VERSION[this.environment][feature];
|
package/package.json
CHANGED
|
@@ -3,6 +3,7 @@ export declare const HEADER_KEY_USER_AGENT = "user-agent";
|
|
|
3
3
|
export declare const COOKIE_KEY_BRIDGE_TO_NATIVE_DATA = "bridgeToNativeData";
|
|
4
4
|
export declare const COOKIE_KEY_BRIDGE_TO_NATIVE_RELOAD = "bridgeToNativeReload";
|
|
5
5
|
export declare const HEADER_KEY_NATIVE_APPVERSION = "app-version";
|
|
6
|
+
export declare const HEADER_KEY_WV_LAUNCH_TIME = "webview-launch-time";
|
|
6
7
|
export declare const QUERY_B2N_NEXT_PAGEID = "b2n-next-page-id";
|
|
7
8
|
export declare const QUERY_B2N_TITLE = "b2n-title";
|
|
8
9
|
export declare const QUERY_B2N_TITLE_DEPRECATED = "title";
|
|
@@ -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.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.HEADER_KEY_WV_LAUNCH_TIME = exports.HEADER_KEY_NATIVE_APPVERSION = exports.COOKIE_KEY_BRIDGE_TO_NATIVE_RELOAD = exports.COOKIE_KEY_BRIDGE_TO_NATIVE_DATA = exports.HEADER_KEY_USER_AGENT = exports.HEADER_KEY_COOKIE = void 0;
|
|
7
7
|
exports.HEADER_KEY_COOKIE = 'cookie';
|
|
8
8
|
exports.HEADER_KEY_USER_AGENT = 'user-agent';
|
|
9
9
|
/*
|
|
@@ -21,6 +21,8 @@ exports.COOKIE_KEY_BRIDGE_TO_NATIVE_RELOAD = 'bridgeToNativeReload';
|
|
|
21
21
|
// * Есть подозрение на некоторую нестабильность — возможно делает это
|
|
22
22
|
// только для первого документа в вебвью-сессии. Исследовать.
|
|
23
23
|
exports.HEADER_KEY_NATIVE_APPVERSION = 'app-version';
|
|
24
|
+
// Ключ заголовка, в котором передается время старта открытия WV экрана в формате timestamp
|
|
25
|
+
exports.HEADER_KEY_WV_LAUNCH_TIME = 'webview-launch-time';
|
|
24
26
|
// Флаг, предписывающий клиентскому коду B2N начать связь
|
|
25
27
|
// с нативным приложением с указанного в этом параметре id, а не с 1.
|
|
26
28
|
// Необходимо для server-side навигации.
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { type NativeParams } from '../types';
|
|
2
1
|
import { type UniversalRequest } from './types';
|
|
3
2
|
/**
|
|
4
3
|
* Парсит запрос, доставая из него данные о нативном приложении,
|
|
@@ -11,4 +10,4 @@ import { type UniversalRequest } from './types';
|
|
|
11
10
|
* Нужно передать функцию, которая средствами используемого веб-сервера добавит заголовок в ответ.
|
|
12
11
|
* b2native с её помощью добавит `Set-Cookie` заголовок с некоторыми данными для своего клиентского кода.
|
|
13
12
|
*/
|
|
14
|
-
export declare function prepareNativeAppDetailsForClient(request: UniversalRequest, setResponseHeader: (headerKey: string, headerValue: string) => void):
|
|
13
|
+
export declare function prepareNativeAppDetailsForClient(request: UniversalRequest, setResponseHeader: (headerKey: string, headerValue: string) => void): any;
|
|
@@ -17,26 +17,30 @@ const utils_1 = require("./utils");
|
|
|
17
17
|
* b2native с её помощью добавит `Set-Cookie` заголовок с некоторыми данными для своего клиентского кода.
|
|
18
18
|
*/
|
|
19
19
|
function prepareNativeAppDetailsForClient(request, setResponseHeader) {
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
// при
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
// Для случая когда передан флаг `reload` куку перезаписывать не нужно, потому что:
|
|
20
|
+
// Поскольку вебвью модули имеют особенность сохранять сессионную куку подолгу,
|
|
21
|
+
// даже после перезагрузки устройства или обновления приложения/ОС, ее значение
|
|
22
|
+
// актуализируется при каждом запросе на сервер. Исключением является вызов `reload()`.
|
|
23
|
+
// В этом случае функция возвращает объект с параметрами, полученными из
|
|
24
|
+
// ранее сохраненной куки, но перезаписываться кука не будет, потому что:
|
|
26
25
|
// 1) Данных NA в запросе с большой вероятностью не будет;
|
|
27
26
|
// 2) клиентская сторона сохранит всё, что нужно в SessionStorage
|
|
28
27
|
const cookieHeader = (0, utils_1.getHeaderValue)(request, query_and_headers_keys_1.HEADER_KEY_COOKIE);
|
|
29
|
-
const nativeParams = parseRequest(request);
|
|
30
28
|
const hasReloadFlag = cookieHeader?.includes(`${query_and_headers_keys_1.COOKIE_KEY_BRIDGE_TO_NATIVE_RELOAD}=true`);
|
|
31
|
-
const shouldUpdateCookie = !(0, utils_1.hasBridgeToNativeDataCookie)(cookieHeader) || !isSameTheme(request, cookieHeader);
|
|
32
|
-
if (!hasReloadFlag && shouldUpdateCookie) {
|
|
33
|
-
const serializedNativeParams = encodeURIComponent(JSON.stringify(nativeParams));
|
|
34
|
-
setResponseHeader('Set-Cookie', `${query_and_headers_keys_1.COOKIE_KEY_BRIDGE_TO_NATIVE_DATA}=${serializedNativeParams}; Path=/`);
|
|
35
|
-
return nativeParams;
|
|
36
|
-
}
|
|
37
29
|
if (hasReloadFlag) {
|
|
38
30
|
setResponseHeader('Set-Cookie', `${query_and_headers_keys_1.COOKIE_KEY_BRIDGE_TO_NATIVE_RELOAD}=false; Max-Age=0; Path=/`);
|
|
31
|
+
const cookieData = (0, utils_1.getBridgeToNativeDataCookie)(cookieHeader);
|
|
32
|
+
if (cookieData) {
|
|
33
|
+
try {
|
|
34
|
+
return JSON.parse(decodeURIComponent(cookieData));
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return parseRequest(request);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
39
40
|
}
|
|
41
|
+
const nativeParams = parseRequest(request);
|
|
42
|
+
const serializedNativeParams = encodeURIComponent(JSON.stringify(nativeParams));
|
|
43
|
+
setResponseHeader('Set-Cookie', `${query_and_headers_keys_1.COOKIE_KEY_BRIDGE_TO_NATIVE_DATA}=${serializedNativeParams}; Path=/`);
|
|
40
44
|
return nativeParams;
|
|
41
45
|
}
|
|
42
46
|
function parseRequest(request) {
|
|
@@ -54,6 +58,7 @@ function parseRequest(request) {
|
|
|
54
58
|
query_and_headers_keys_1.QUERY_B2N_TITLE_DEPRECATED,
|
|
55
59
|
]);
|
|
56
60
|
const appVersionFromHeaders = (0, utils_1.getHeaderValue)(request, query_and_headers_keys_1.HEADER_KEY_NATIVE_APPVERSION);
|
|
61
|
+
const webviewLaunchTime = (0, utils_1.parseHeaderTimestamp)((0, utils_1.getHeaderValue)(request, query_and_headers_keys_1.HEADER_KEY_WV_LAUNCH_TIME));
|
|
57
62
|
const nativeParams = {
|
|
58
63
|
originalWebviewParams,
|
|
59
64
|
theme: theme || 'light',
|
|
@@ -84,20 +89,8 @@ function parseRequest(request) {
|
|
|
84
89
|
else if (typeof deprecatedTitle === 'string') {
|
|
85
90
|
nativeParams.title = deprecatedTitle;
|
|
86
91
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
// Возвращает результат сравнения темы полученной, из query-параметров
|
|
90
|
-
// с темой, полученной из cookie
|
|
91
|
-
function isSameTheme(request, cookies) {
|
|
92
|
-
if (!cookies)
|
|
93
|
-
return false;
|
|
94
|
-
const [themeFromQuery] = (0, utils_1.getQueryValues)(request, [query_and_headers_keys_1.QUERY_NATIVE_THEME]);
|
|
95
|
-
const parsedCookies = (0, utils_1.parseCookies)(cookies);
|
|
96
|
-
const bridgeCookie = parsedCookies[query_and_headers_keys_1.COOKIE_KEY_BRIDGE_TO_NATIVE_DATA];
|
|
97
|
-
try {
|
|
98
|
-
return JSON.parse(bridgeCookie || '{}').theme === themeFromQuery;
|
|
99
|
-
}
|
|
100
|
-
catch (_) {
|
|
101
|
-
return false;
|
|
92
|
+
if (webviewLaunchTime) {
|
|
93
|
+
nativeParams.webviewLaunchTime = webviewLaunchTime;
|
|
102
94
|
}
|
|
95
|
+
return nativeParams;
|
|
103
96
|
}
|
package/server/utils.d.ts
CHANGED
|
@@ -22,4 +22,15 @@ export declare function getQueryValues(request: UniversalRequest, queryKeys: str
|
|
|
22
22
|
* @param request Объект запроса (Request или IncomingMessage).
|
|
23
23
|
*/
|
|
24
24
|
export declare function hasBridgeToNativeDataCookie(cookieHeader: string | null): boolean;
|
|
25
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Преобразует значение из заголовка в timestamp
|
|
27
|
+
*
|
|
28
|
+
* @param headerValue Значение заголовка
|
|
29
|
+
*/
|
|
30
|
+
export declare function parseHeaderTimestamp(headerValue: string | number | null): number | null;
|
|
31
|
+
/**
|
|
32
|
+
* Возвращает значение для нужной куки
|
|
33
|
+
*
|
|
34
|
+
* @param cookieName Имя куки
|
|
35
|
+
*/
|
|
36
|
+
export declare const getBridgeToNativeDataCookie: (cookieName: string | null) => string | undefined;
|
package/server/utils.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getBridgeToNativeDataCookie = void 0;
|
|
3
4
|
exports.getHeaderValue = getHeaderValue;
|
|
4
5
|
exports.getQueryValues = getQueryValues;
|
|
5
6
|
exports.hasBridgeToNativeDataCookie = hasBridgeToNativeDataCookie;
|
|
6
|
-
exports.
|
|
7
|
+
exports.parseHeaderTimestamp = parseHeaderTimestamp;
|
|
8
|
+
const query_and_headers_keys_1 = require("../query-and-headers-keys");
|
|
7
9
|
const regexp_patterns_1 = require("./regexp-patterns");
|
|
8
10
|
/**
|
|
9
11
|
* На основе объекта запроса любого типа возвращает
|
|
@@ -41,13 +43,33 @@ function getQueryValues(request, queryKeys) {
|
|
|
41
43
|
function hasBridgeToNativeDataCookie(cookieHeader) {
|
|
42
44
|
return Boolean(cookieHeader && regexp_patterns_1.bridgeToNativeDataCookieExistencePattern.test(cookieHeader));
|
|
43
45
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
return
|
|
52
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Преобразует значение из заголовка в timestamp
|
|
48
|
+
*
|
|
49
|
+
* @param headerValue Значение заголовка
|
|
50
|
+
*/
|
|
51
|
+
function parseHeaderTimestamp(headerValue) {
|
|
52
|
+
if (!headerValue || (typeof headerValue === 'string' && !headerValue?.trim()))
|
|
53
|
+
return null;
|
|
54
|
+
const timestamp = Number(headerValue);
|
|
55
|
+
return Number.isFinite(timestamp) ? timestamp : null;
|
|
53
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Возвращает значение для нужной куки
|
|
59
|
+
*
|
|
60
|
+
* @param cookieName Имя куки
|
|
61
|
+
*/
|
|
62
|
+
const getBridgeToNativeDataCookie = (cookieName) => {
|
|
63
|
+
if (!cookieName) {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
const cookies = cookieName.split(';');
|
|
67
|
+
for (const cookie of cookies) {
|
|
68
|
+
const [key, value] = cookie.trim().split('=');
|
|
69
|
+
if (key === query_and_headers_keys_1.COOKIE_KEY_BRIDGE_TO_NATIVE_DATA) {
|
|
70
|
+
return value;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return undefined;
|
|
74
|
+
};
|
|
75
|
+
exports.getBridgeToNativeDataCookie = getBridgeToNativeDataCookie;
|