@alfalab/bridge-to-native 0.0.2-beta-1c5826f

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/constants.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { NativeFeaturesFromVersion } from './types';
2
+ export declare const CLOSE_WEBVIEW_SEARCH_KEY = "closeWebView";
3
+ export declare const CLOSE_WEBVIEW_SEARCH_VALUE = "true";
4
+ export declare const PREVIOUS_B2N_STATE_STORAGE_KEY = "previousBridgeToNativeState";
5
+ export declare const PREVIOUS_NATIVE_NAVIGATION_AND_TITLE_STATE_STORAGE_KEY = "previousNativeNavigationAndTitleState";
6
+ export declare const versionToIosAppId: {
7
+ readonly '0.0.0': "YWxmYWJhbms=";
8
+ readonly '12.22.0': "YWNvbmNpZXJnZQ==";
9
+ readonly '12.26.0': "a2l0dHljYXNo";
10
+ readonly '12.31.0': "YXdlYXNzaXN0";
11
+ };
12
+ export declare const nativeFeaturesFromVersion: NativeFeaturesFromVersion;
package/constants.js ADDED
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ 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 = void 0;
4
+ exports.CLOSE_WEBVIEW_SEARCH_KEY = 'closeWebView';
5
+ exports.CLOSE_WEBVIEW_SEARCH_VALUE = 'true';
6
+ exports.PREVIOUS_B2N_STATE_STORAGE_KEY = 'previousBridgeToNativeState';
7
+ exports.PREVIOUS_NATIVE_NAVIGATION_AND_TITLE_STATE_STORAGE_KEY = 'previousNativeNavigationAndTitleState';
8
+ exports.versionToIosAppId = {
9
+ '0.0.0': 'YWxmYWJhbms=',
10
+ '12.22.0': 'YWNvbmNpZXJnZQ==',
11
+ '12.26.0': 'a2l0dHljYXNo',
12
+ '12.31.0': 'YXdlYXNzaXN0',
13
+ };
14
+ exports.nativeFeaturesFromVersion = {
15
+ android: {
16
+ linksInBrowser: {
17
+ nativeFeatureFtKey: 'linksInBrowserAndroid',
18
+ fromVersion: '11.71.0',
19
+ },
20
+ geolocation: { fromVersion: '11.71.0' },
21
+ },
22
+ ios: {
23
+ linksInBrowser: {
24
+ nativeFeatureFtKey: 'linksInBrowserIos',
25
+ fromVersion: '13.3.0',
26
+ },
27
+ geolocation: { fromVersion: '0.0.0' },
28
+ },
29
+ };
package/index.d.ts ADDED
@@ -0,0 +1,64 @@
1
+ import { NativeFallbacks } from './native-fallbacks';
2
+ import { HandleRedirect, NativeNavigationAndTitle } from './native-navigation-and-title';
3
+ import type { Environment, NativeFeatureKey, NativeFeaturesFts, NativeParams } from './types';
4
+ declare type Theme = 'light' | 'dark';
5
+ /**
6
+ * Этот класс - абстракция для связи веб приложения с нативом и предназначен ТОЛЬКО
7
+ * для использования в вебвью окружении.
8
+ */
9
+ export declare class BridgeToNative {
10
+ nativeFeaturesFts: NativeFeaturesFts;
11
+ readonly AndroidBridge: {
12
+ setPageSettings: (params: string) => void;
13
+ } | undefined;
14
+ readonly environment: Environment;
15
+ readonly nativeFallbacks: NativeFallbacks;
16
+ private nextPageId;
17
+ private _nativeNavigationAndTitle;
18
+ private _originalWebviewParams;
19
+ private _appVersion;
20
+ private _iosAppId?;
21
+ private _theme;
22
+ private _handleRedirect;
23
+ constructor(nativeFeaturesFts: NativeFeaturesFts, handleRedirect: HandleRedirect, nativeParams?: NativeParams);
24
+ get theme(): Theme;
25
+ get appVersion(): string;
26
+ get iosAppId(): string | undefined;
27
+ get nativeNavigationAndTitle(): NativeNavigationAndTitle;
28
+ get originalWebviewParams(): string;
29
+ /**
30
+ * Метод, проверяющий, можно ли использовать нативную функциональность в текущей версии приложения.
31
+ *
32
+ * @param feature Название функциональности, которую нужно проверить.
33
+ */
34
+ canUseNativeFeature(feature: NativeFeatureKey): boolean;
35
+ /**
36
+ * Метод, отправляющий сигнал нативу, что нужно закрыть текущее вебвью.
37
+ */
38
+ closeWebview(): void;
39
+ /**
40
+ * Сравнивает текущую версию приложения с переданной.
41
+ *
42
+ * @param versionToCompare Версия, с которой нужно сравнить текущую.
43
+ * @returns `true` – текущая версия больше или равняется переданной,
44
+ * `false` – текущая версия ниже.
45
+ */
46
+ isCurrentVersionHigherOrEqual(versionToCompare: string): 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
+ }
64
+ export {};
package/index.js ADDED
@@ -0,0 +1,144 @@
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(nativeFeaturesFts = { 'linksInBrowserAndroid': true, 'linksInBrowserIos': true }, handleRedirect, nativeParams) {
15
+ this.nativeFeaturesFts = nativeFeaturesFts;
16
+ // Webview, запущенное в Android окружении имеет объект `Android` в window.
17
+ this.AndroidBridge = window.Android;
18
+ this.environment = this.AndroidBridge ? 'android' : 'ios';
19
+ const previousState = !!sessionStorage.getItem(constants_1.PREVIOUS_B2N_STATE_STORAGE_KEY);
20
+ if (previousState) {
21
+ this.restorePreviousState();
22
+ this.nativeFallbacks = new native_fallbacks_1.NativeFallbacks(this);
23
+ return;
24
+ }
25
+ this._appVersion =
26
+ nativeParams && (0, utils_1.isValidVersionFormat)(nativeParams === null || nativeParams === void 0 ? void 0 : nativeParams.appVersion)
27
+ ? nativeParams.appVersion
28
+ : '0.0.0';
29
+ this._iosAppId = this.getIosAppId(nativeParams === null || nativeParams === void 0 ? void 0 : nativeParams.iosAppId);
30
+ this._theme = (nativeParams === null || nativeParams === void 0 ? void 0 : nativeParams.theme) === 'dark' ? 'dark' : 'light';
31
+ this._originalWebviewParams = (nativeParams === null || nativeParams === void 0 ? void 0 : nativeParams.originalWebviewParams) || '';
32
+ this._nativeNavigationAndTitle = new native_navigation_and_title_1.NativeNavigationAndTitle(this, nativeParams ? nativeParams.nextPageId : null, nativeParams === null || nativeParams === void 0 ? void 0 : nativeParams.title, handleRedirect);
33
+ this._handleRedirect = handleRedirect;
34
+ this.nextPageId = nativeParams ? nativeParams.nextPageId : null;
35
+ this.nativeFallbacks = new native_fallbacks_1.NativeFallbacks(this);
36
+ }
37
+ get theme() {
38
+ return this._theme;
39
+ }
40
+ get appVersion() {
41
+ return this._appVersion;
42
+ }
43
+ get iosAppId() {
44
+ return this._iosAppId;
45
+ }
46
+ get nativeNavigationAndTitle() {
47
+ return this._nativeNavigationAndTitle;
48
+ }
49
+ get originalWebviewParams() {
50
+ return this._originalWebviewParams;
51
+ }
52
+ /**
53
+ * Метод, проверяющий, можно ли использовать нативную функциональность в текущей версии приложения.
54
+ *
55
+ * @param feature Название функциональности, которую нужно проверить.
56
+ */
57
+ canUseNativeFeature(feature) {
58
+ const { nativeFeatureFtKey, fromVersion } = constants_1.nativeFeaturesFromVersion[this.environment][feature];
59
+ if (nativeFeatureFtKey && !this.nativeFeaturesFts[nativeFeatureFtKey]) {
60
+ return false;
61
+ }
62
+ return this.isCurrentVersionHigherOrEqual(fromVersion);
63
+ }
64
+ /**
65
+ * Метод, отправляющий сигнал нативу, что нужно закрыть текущее вебвью.
66
+ */
67
+ // eslint-disable-next-line class-methods-use-this
68
+ closeWebview() {
69
+ const originalPageUrl = new URL(window.location.href);
70
+ originalPageUrl.searchParams.set(constants_1.CLOSE_WEBVIEW_SEARCH_KEY, constants_1.CLOSE_WEBVIEW_SEARCH_VALUE);
71
+ window.location.href = originalPageUrl.toString();
72
+ }
73
+ /**
74
+ * Сравнивает текущую версию приложения с переданной.
75
+ *
76
+ * @param versionToCompare Версия, с которой нужно сравнить текущую.
77
+ * @returns `true` – текущая версия больше или равняется переданной,
78
+ * `false` – текущая версия ниже.
79
+ */
80
+ isCurrentVersionHigherOrEqual(versionToCompare) {
81
+ if (!(0, utils_1.isValidVersionFormat)(versionToCompare)) {
82
+ return false;
83
+ }
84
+ const matchPattern = /(\d+)\.(\d+)\.(\d+)/;
85
+ const [, ...appVersionComponents] = this._appVersion.match(matchPattern); // Формат версии проверен в конструкторе, можно смело убирать `null` из типа.
86
+ const [, ...versionToCompareComponents] = versionToCompare.match(matchPattern);
87
+ for (let i = 0; i < appVersionComponents.length; i++) {
88
+ if (appVersionComponents[i] !== versionToCompareComponents[i]) {
89
+ return appVersionComponents[i] >= versionToCompareComponents[i];
90
+ }
91
+ }
92
+ return true;
93
+ }
94
+ /**
95
+ * Сохраняет текущее состояние BridgeToNative в sessionStorage.
96
+ * Так же сохраняет текущее состояние nativeNavigationAndTitle.
97
+ */
98
+ saveCurrentState() {
99
+ // В nativeNavigationAndTitle этот метод отмечен модификатором доступа private дабы не торчал наружу, но тут его нужно вызвать
100
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
101
+ // @ts-ignore
102
+ this._nativeNavigationAndTitle.saveCurrentState();
103
+ const currentState = {
104
+ appVersion: this._appVersion,
105
+ theme: this._theme,
106
+ nextPageId: this.nextPageId,
107
+ originalWebviewParams: this._originalWebviewParams || '',
108
+ iosAppId: this._iosAppId,
109
+ };
110
+ sessionStorage.setItem(constants_1.PREVIOUS_B2N_STATE_STORAGE_KEY, JSON.stringify(currentState));
111
+ }
112
+ /**
113
+ * Возвращает схему приложения в iOS окружении, на основе версии.
114
+ *
115
+ * @param knownIosAppId Тип iOS приложения, если он известен.
116
+ * @returns Тип приложения, `undefined` для Android окружения.
117
+ */
118
+ getIosAppId(knownIosAppId) {
119
+ if (this.environment !== 'ios') {
120
+ return undefined;
121
+ }
122
+ if (knownIosAppId) {
123
+ return knownIosAppId;
124
+ }
125
+ const keys = Object.keys(constants_1.versionToIosAppId);
126
+ const rightKey = [...keys].reverse().find((version) => this.isCurrentVersionHigherOrEqual(version)) ||
127
+ keys[0];
128
+ return atob(constants_1.versionToIosAppId[rightKey]);
129
+ }
130
+ /**
131
+ * Восстанавливает свое предыдущее состояние из sessionStorage
132
+ */
133
+ restorePreviousState() {
134
+ const previousState = JSON.parse(sessionStorage.getItem(constants_1.PREVIOUS_B2N_STATE_STORAGE_KEY) || '');
135
+ this._appVersion = previousState.appVersion;
136
+ this._iosAppId = previousState.iosAppId;
137
+ this._theme = previousState.theme;
138
+ this._originalWebviewParams = previousState.originalWebviewParams;
139
+ this.nextPageId = previousState.nextPageId;
140
+ this._nativeNavigationAndTitle = new native_navigation_and_title_1.NativeNavigationAndTitle(this, previousState.nextPageId, '', this._handleRedirect);
141
+ sessionStorage.removeItem(constants_1.PREVIOUS_B2N_STATE_STORAGE_KEY);
142
+ }
143
+ }
144
+ exports.BridgeToNative = BridgeToNative;
@@ -0,0 +1,59 @@
1
+ import type { BridgeToNative } from '.';
2
+ import { PdfType } from './types';
3
+ /**
4
+ * Класс содержит реализацию обходных путей для веб-фич, которые не работают в нативном-вебвью.
5
+ */
6
+ export declare class NativeFallbacks {
7
+ private b2n;
8
+ constructor(b2n: BridgeToNative);
9
+ /**
10
+ * Метод, возвращающий пропсы для ссылок, ведущих на ВНЕШНИЙ ресурс. Которые просто
11
+ * нужно «подмешать» к ссылке в JSX:
12
+ *
13
+ * ```
14
+ * <a {...bridgeToNative.nativeFallbacks.getExternalLinkProps('https://ya.ru')}>Link to external feature</a>
15
+ * ```
16
+ * Либо просто достать интересующие поля - onClick или href
17
+ * ```
18
+ * const {onClick, href} = bridgeToNative.nativeFallbacks.getExternalLinkProps(url, clickHandler)
19
+ * document.querySelector('.myLink').onclick = onClick;
20
+ * <a {...bridgeToNative.nativeFallbacks.getExternalLinkProps('https://ya.ru')}>Link to external feature</a>
21
+ * ```
22
+ * В разных OS и разных версиях приложения, открытие ресурса будет работать по-разному:
23
+ *
24
+ * - Если текущая версия приложения может открыть ссылку в браузере,
25
+ * обогащаем URL специальным query-параметром (`target=_blank` в приложении не работает).
26
+ * - Если это iOS, меняем URL на диплинк, который откроет ссылку в новом вебвью, поверх текущего.
27
+ * К первому-вебвью, пользователь вернётся, когда закроет второе вебвью с внешним ресурсом.
28
+ * - В старых приложениях на Андроид – URL не меняем, но добавляем `onClick` для сбрасывания синхронизации
29
+ * навигации с приложением. Это «фолбэк-сценарий» с плохим UX (сайт полностью выпадает из истории), но другого способа нет.
30
+ *
31
+ * @param link Строка - валидный урл.
32
+ * @param onClick Дополнительный обработчик на клик, например, для отправки метрики.
33
+ * Внимание! Не факт, что в «фолбэк-сценарии» асинхронная операция будет выполнена (метрика отправлена)!
34
+ * @returns Пропсы для ссылки в вебвью окружении.
35
+ */
36
+ getExternalLinkProps(link: string, onClick?: () => void): {
37
+ href: string;
38
+ onClick: (() => void) | undefined;
39
+ };
40
+ /**
41
+ * Метод для открытия PDF в нативном вьювере.
42
+ *
43
+ * Есть нюансы с версиями приложения, OS устройства.
44
+ * Надо тестировать по моде статистики.
45
+ *
46
+ * @param url ссылка на pdf
47
+ * @param type тип pdf ссылки
48
+ * @param title название pdf файла
49
+ */
50
+ openPdf(url: string, type?: PdfType, title?: string): void;
51
+ /**
52
+ * Метод, для перехода на ВНЕШНИЙ ресурс.
53
+ *
54
+ * См. описание в `getExternalLinkProps`, чтобы узнать, как выбирается способ для перехода.
55
+ *
56
+ * @param link Строка - валидный урл.
57
+ */
58
+ visitExternalResource(link: string): void;
59
+ }
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NativeFallbacks = void 0;
4
+ const utils_1 = require("./utils");
5
+ /**
6
+ * Класс содержит реализацию обходных путей для веб-фич, которые не работают в нативном-вебвью.
7
+ */
8
+ class NativeFallbacks {
9
+ constructor(b2n) {
10
+ this.b2n = b2n;
11
+ }
12
+ /**
13
+ * Метод, возвращающий пропсы для ссылок, ведущих на ВНЕШНИЙ ресурс. Которые просто
14
+ * нужно «подмешать» к ссылке в JSX:
15
+ *
16
+ * ```
17
+ * <a {...bridgeToNative.nativeFallbacks.getExternalLinkProps('https://ya.ru')}>Link to external feature</a>
18
+ * ```
19
+ * Либо просто достать интересующие поля - onClick или href
20
+ * ```
21
+ * const {onClick, href} = bridgeToNative.nativeFallbacks.getExternalLinkProps(url, clickHandler)
22
+ * document.querySelector('.myLink').onclick = onClick;
23
+ * <a {...bridgeToNative.nativeFallbacks.getExternalLinkProps('https://ya.ru')}>Link to external feature</a>
24
+ * ```
25
+ * В разных OS и разных версиях приложения, открытие ресурса будет работать по-разному:
26
+ *
27
+ * - Если текущая версия приложения может открыть ссылку в браузере,
28
+ * обогащаем URL специальным query-параметром (`target=_blank` в приложении не работает).
29
+ * - Если это iOS, меняем URL на диплинк, который откроет ссылку в новом вебвью, поверх текущего.
30
+ * К первому-вебвью, пользователь вернётся, когда закроет второе вебвью с внешним ресурсом.
31
+ * - В старых приложениях на Андроид – URL не меняем, но добавляем `onClick` для сбрасывания синхронизации
32
+ * навигации с приложением. Это «фолбэк-сценарий» с плохим UX (сайт полностью выпадает из истории), но другого способа нет.
33
+ *
34
+ * @param link Строка - валидный урл.
35
+ * @param onClick Дополнительный обработчик на клик, например, для отправки метрики.
36
+ * Внимание! Не факт, что в «фолбэк-сценарии» асинхронная операция будет выполнена (метрика отправлена)!
37
+ * @returns Пропсы для ссылки в вебвью окружении.
38
+ */
39
+ getExternalLinkProps(link, onClick) {
40
+ const { iosAppId } = this.b2n;
41
+ const url = (0, utils_1.getUrlInstance)(link);
42
+ if (this.b2n.canUseNativeFeature('linksInBrowser')) {
43
+ url.searchParams.append('openInBrowser', 'true');
44
+ return { href: url.href, onClick };
45
+ }
46
+ if (iosAppId) {
47
+ return {
48
+ href: `${iosAppId}://webFeature?type=recommendation&url=${encodeURIComponent(url.href)}`,
49
+ onClick,
50
+ };
51
+ }
52
+ return {
53
+ href: url.href,
54
+ onClick: () => {
55
+ var _a;
56
+ onClick === null || onClick === void 0 ? void 0 : onClick();
57
+ (_a = this.b2n.nativeNavigationAndTitle) === null || _a === void 0 ? void 0 : _a.setInitialView('');
58
+ },
59
+ };
60
+ }
61
+ /**
62
+ * Метод для открытия PDF в нативном вьювере.
63
+ *
64
+ * Есть нюансы с версиями приложения, OS устройства.
65
+ * Надо тестировать по моде статистики.
66
+ *
67
+ * @param url ссылка на pdf
68
+ * @param type тип pdf ссылки
69
+ * @param title название pdf файла
70
+ */
71
+ openPdf(url, type = 'pdfFile', title) {
72
+ const params = new URLSearchParams();
73
+ params.append('type', type);
74
+ params.append('url', decodeURIComponent(url));
75
+ if (title) {
76
+ params.append('title', title.replace(/\s/g, '_'));
77
+ }
78
+ let replaceUrl = url;
79
+ const paramsStr = params.toString();
80
+ const { environment, iosAppId } = this.b2n;
81
+ if (environment === 'ios') {
82
+ replaceUrl = `${iosAppId}:///dashboard/pdf_viewer?${paramsStr}`;
83
+ }
84
+ // У андройда через диплинк открывается, но предыдущий экран затирается.
85
+ // Поэтому мы открываем base64 через конвертирование в бинарный pdf (через ручки сервиса)
86
+ // Это позволяет перейти назад к вебвью
87
+ if (environment === 'android' && type === 'base64') {
88
+ replaceUrl = `/services/base64-to-pdf?${paramsStr}`;
89
+ }
90
+ window.open(replaceUrl);
91
+ }
92
+ /**
93
+ * Метод, для перехода на ВНЕШНИЙ ресурс.
94
+ *
95
+ * См. описание в `getExternalLinkProps`, чтобы узнать, как выбирается способ для перехода.
96
+ *
97
+ * @param link Строка - валидный урл.
98
+ */
99
+ visitExternalResource(link) {
100
+ var _a;
101
+ const { iosAppId } = this.b2n;
102
+ const url = (0, utils_1.getUrlInstance)(link);
103
+ if (this.b2n.canUseNativeFeature('linksInBrowser')) {
104
+ url.searchParams.append('openInBrowser', 'true');
105
+ window.location.replace(url.href);
106
+ }
107
+ else if (iosAppId) {
108
+ window.location.replace(`${iosAppId}://webFeature?type=recommendation&url=${encodeURIComponent(url.href)}`);
109
+ }
110
+ else {
111
+ (_a = this.b2n.nativeNavigationAndTitle) === null || _a === void 0 ? void 0 : _a.setInitialView('');
112
+ window.location.replace(url.href);
113
+ }
114
+ }
115
+ }
116
+ exports.NativeFallbacks = NativeFallbacks;
@@ -0,0 +1,142 @@
1
+ import { BridgeToNative } from '.';
2
+ export declare type HandleRedirect = (appName: string, path?: string, params?: Record<string, string>) => void;
3
+ /**
4
+ * Класс, отвечающий за взаимодействие с нативными элементами в приложении – заголовком
5
+ * и нативной кнопкой назад.
6
+ */
7
+ export declare class NativeNavigationAndTitle {
8
+ private b2n;
9
+ private nativeHistoryStack;
10
+ private numOfBackSteps;
11
+ private lastSetPageSettingsParams;
12
+ private handleWindowRedirect;
13
+ constructor(b2n: BridgeToNative, pageId: number | null, initialNativeTitle: string | undefined, handleWindowRedirect: HandleRedirect);
14
+ /**
15
+ * Метод, вызывающий `history.back()` или закрывающий вебвью, если нет записей
16
+ * в истории переходов.
17
+ */
18
+ goBack(): void;
19
+ /**
20
+ * Метод, вызывающий history.go(-колл. шагов назад) и модифицирует внутреннее
21
+ * состояние, чтобы в дальнейшем зарегистририровать этот переход в приложении.
22
+ *
23
+ * @param stepsNumber Количество шагов назад.
24
+ * Возможно передача как положительного, так и отрицательного числа.
25
+ * 0 будет проигнорирован.
26
+ * @param autocloseWebview Флаг – закрывать ли вебвью автоматически,
27
+ * если переданное кол-во шагов будет больше, чем записей в истории.
28
+ */
29
+ goBackAFewSteps(stepsNumber: number, autocloseWebview?: boolean): void;
30
+ /**
31
+ * @param path Путь для перехода на функциональность внутри приложения.
32
+ */
33
+ handleRedirect(path: string): void;
34
+ /**
35
+ * В этом варианте аргументы 2,3,4 соответствуют аргументам 1,2,3 метода `src/shared/utils/handle-redirect`.
36
+ *
37
+ * @param pageTitle Заголовок, который нужно отрисовать в приложении.
38
+ * @param appName См. первый параметр `src/handle-redirect.ts`.
39
+ * @param path См. второй параметр `src/handle-redirect.ts`.
40
+ * @param params См. третий параметр `src/handle-redirect.ts`.
41
+ */
42
+ handleRedirect(pageTitle: string, appName: string, path?: string, params?: Record<string, string>): void;
43
+ /**
44
+ * Информирует натив, что веб находится на первом экране (сбрасывает историю переходов, не влияя на браузерную
45
+ * историю), а значит следующее нажатие на кнопку "Назад" в нативне закроет вебвью.
46
+ *
47
+ * @param pageTitle Заголовок, который нужно отрисовать в нативе.
48
+ */
49
+ setInitialView(pageTitle?: string): void;
50
+ /**
51
+ * Метод для смены заголовка в нативе без влияния на историю переходов.
52
+ *
53
+ * @param pageTitle Заголовок, который нужно отрисовать в нативе.
54
+ */
55
+ setTitle(pageTitle: string): void;
56
+ /**
57
+ * Метод для открытия второго web приложения в рамках
58
+ * одной вебвью сессии
59
+ * сохраняет все текущее состояние текущего экземпляра bridgeToAm и AmNavigationAndTitle в sessionStorage, а
60
+ * так же наполняет url необходимыми query параметрами. Работает только в Android окружении.
61
+ * В IOS окружении будет открыто новое webview поверх текущего.
62
+ * @param url адрес второго web приложения к которому перед переходом на него будут добавлены
63
+ * все initial query параметры от натива и параметр nextPageId (Android)
64
+ */
65
+ navigateInsideASharedSession(url: string): void;
66
+ /**
67
+ * ПОКА НЕ ИСПОЛЬЗОВАТЬ В ПРОДЕ (МЕТОД НЕ РАБОТАЕТ КОРРЕКТНО НА ANDROID)
68
+ * НА ANDROID ПРИ ПЕРЕЗАГРУЗКЕ СТРАНИЦЫ В ИСТРИЮ ЛОЖИТСЯ + ЕЩЕ ОДИН ЛИШНИЙ ЭЛЕМЕНТ
69
+ * Данный метод будет дорабатываться отдельной задачей
70
+ * Безопасный способ для перезагрузки страницы.
71
+ * Производит предварительное сохранение текущего состояния
72
+ * BridgeToNative и NativeNavigationAndTitle перед вызовом location.reload()
73
+ */
74
+ reloadPage(): void;
75
+ /**
76
+ * Метод для сохранения текущего состояния NativeNavigationAndTitle в sessionStorage.
77
+ */
78
+ private saveCurrentState;
79
+ /**
80
+ * Метод, вычисляющий `pageId`, который нужно послать в приложение
81
+ * для правильной синхронизации с нативной-кнопкой "Назад".
82
+ *
83
+ * @param purpose Цель взаимодействия с приложением.
84
+ * @returns Правильный pageId.
85
+ */
86
+ private getNativePageId;
87
+ /**
88
+ * Вспомогательный метод для `getNativePageId` initialization кейса.
89
+ *
90
+ * @returns Правильный pageId.
91
+ */
92
+ private getNativePageIdForInitialization;
93
+ /**
94
+ * Вспомогательный метод для `getNativePageId` navigation кейса.
95
+ *
96
+ * @returns Правильный pageId.
97
+ */
98
+ private getNativePageIdForNavigation;
99
+ /**
100
+ * Вспомогательный метод для `getNativePageId` only-title кейса.
101
+ *
102
+ * @returns Правильный pageId.
103
+ */
104
+ private getNativePageIdForTitleReplacing;
105
+ /**
106
+ * Обработчик для `window.onpopstate` события. Который сработает
107
+ * после нажатия на кнопку "Назад" в нативе, вызова `history.back()` и `history.go(-x)`.
108
+ */
109
+ private handleBack;
110
+ /**
111
+ * Синхронизирует состояние истории переходов и заголовок с приложением.
112
+ *
113
+ * @param pageTitle Заголовок, который нужно отрисовать в приложении.
114
+ * @param purpose Цель взаимодействия с приложением.
115
+ */
116
+ private syncHistoryWithNative;
117
+ /**
118
+ * Метод для перехода в веб из другого веб приложения в рамках
119
+ * одной вебвью сессии
120
+ * @param pageId - Номер текущего page который нужно отправить в приложение
121
+ * @param title - Title текущего page который нужно отправить в приложение
122
+ */
123
+ private supportSharedSession;
124
+ /**
125
+ * Восстанавливает свое предыдущее состояние nativeHistoryStack и title из sessionStorage
126
+ */
127
+ private restorePreviousState;
128
+ /**
129
+ * Вспомогательный метод для setInitialView, supportSharedSession
130
+ * переназначает обработчик @handleBack для `window.onpopstate` события
131
+ */
132
+ private reassignPopstateListener;
133
+ /**
134
+ * Вспомогательный метод для navigateInsideASharedSession.
135
+ * Подготавливает внешнюю ссылку в рамках контракта для совместной работы веб-приложений в
136
+ * рамках одной вебвью сессии
137
+ * @param url - url иного веб приложения
138
+ * @return подготовленная согласно контракту ссылка на иное веб приложение с initial query
139
+ * параметрами от нативе, а так же nextPageId
140
+ */
141
+ private prepareExternalLinkBeforeOpen;
142
+ }
@@ -0,0 +1,310 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NativeNavigationAndTitle = void 0;
4
+ const constants_1 = require("./constants");
5
+ const utils_1 = require("./utils");
6
+ /**
7
+ * Класс, отвечающий за взаимодействие с нативными элементами в приложении – заголовком
8
+ * и нативной кнопкой назад.
9
+ */
10
+ class NativeNavigationAndTitle {
11
+ constructor(b2n, pageId, initialNativeTitle = '', handleWindowRedirect) {
12
+ this.b2n = b2n;
13
+ this.nativeHistoryStack = [''];
14
+ this.numOfBackSteps = 1;
15
+ // Тут сохраняются параметры, которые в последний раз были отправлены в приложение.
16
+ // Просто, чтобы не слать одинаковые сигналы в приложение.
17
+ this.lastSetPageSettingsParams = '';
18
+ this.handleBack = this.handleBack.bind(this);
19
+ this.handleWindowRedirect = handleWindowRedirect;
20
+ const previousState = !!sessionStorage.getItem(constants_1.PREVIOUS_NATIVE_NAVIGATION_AND_TITLE_STATE_STORAGE_KEY);
21
+ if (pageId) {
22
+ this.supportSharedSession(pageId, initialNativeTitle);
23
+ }
24
+ else if (previousState) {
25
+ this.restorePreviousState();
26
+ }
27
+ else {
28
+ this.setInitialView(initialNativeTitle);
29
+ }
30
+ }
31
+ /**
32
+ * Метод, вызывающий `history.back()` или закрывающий вебвью, если нет записей
33
+ * в истории переходов.
34
+ */
35
+ goBack() {
36
+ this.goBackAFewSteps(-1, true);
37
+ }
38
+ /**
39
+ * Метод, вызывающий history.go(-колл. шагов назад) и модифицирует внутреннее
40
+ * состояние, чтобы в дальнейшем зарегистририровать этот переход в приложении.
41
+ *
42
+ * @param stepsNumber Количество шагов назад.
43
+ * Возможно передача как положительного, так и отрицательного числа.
44
+ * 0 будет проигнорирован.
45
+ * @param autocloseWebview Флаг – закрывать ли вебвью автоматически,
46
+ * если переданное кол-во шагов будет больше, чем записей в истории.
47
+ */
48
+ goBackAFewSteps(stepsNumber, autocloseWebview = false) {
49
+ if (!stepsNumber) {
50
+ return;
51
+ }
52
+ const stepsToBack = Math.abs(stepsNumber);
53
+ const maxStepsToBack = this.nativeHistoryStack.length - 1;
54
+ if (stepsToBack > maxStepsToBack) {
55
+ if (autocloseWebview) {
56
+ this.b2n.closeWebview();
57
+ return;
58
+ }
59
+ this.numOfBackSteps = maxStepsToBack;
60
+ }
61
+ else {
62
+ this.numOfBackSteps = stepsToBack;
63
+ }
64
+ window.history.go(-this.numOfBackSteps);
65
+ }
66
+ /**
67
+ * Метод вызывает `src/shared/utils/handle-redirect` из `newclick-host-ui`
68
+ * и регистрирует этот переход в приложении, чтобы кнопка «Назад» в Нативе вызывала
69
+ * переход назад в вебе.
70
+ */
71
+ handleRedirect(pageTitleOrPath, appName, path, params) {
72
+ if (appName) {
73
+ this.handleWindowRedirect(appName, path, params);
74
+ }
75
+ else {
76
+ const { appName: extractedAppName, path: extractedPath, query: extractedQuery, } = (0, utils_1.extractAppNameRouteAndQuery)(pageTitleOrPath);
77
+ this.handleWindowRedirect(extractedAppName, extractedPath, extractedQuery);
78
+ }
79
+ const title = appName ? pageTitleOrPath : '';
80
+ this.nativeHistoryStack.push(title);
81
+ this.syncHistoryWithNative(title, 'navigation');
82
+ }
83
+ /**
84
+ * Информирует натив, что веб находится на первом экране (сбрасывает историю переходов, не влияя на браузерную
85
+ * историю), а значит следующее нажатие на кнопку "Назад" в нативне закроет вебвью.
86
+ *
87
+ * @param pageTitle Заголовок, который нужно отрисовать в нативе.
88
+ */
89
+ setInitialView(pageTitle = '') {
90
+ this.nativeHistoryStack = [pageTitle];
91
+ this.syncHistoryWithNative(pageTitle, 'initialization');
92
+ this.reassignPopstateListener();
93
+ }
94
+ /**
95
+ * Метод для смены заголовка в нативе без влияния на историю переходов.
96
+ *
97
+ * @param pageTitle Заголовок, который нужно отрисовать в нативе.
98
+ */
99
+ setTitle(pageTitle) {
100
+ this.nativeHistoryStack[this.nativeHistoryStack.length - 1] = pageTitle;
101
+ this.syncHistoryWithNative(pageTitle, 'title-replacing');
102
+ }
103
+ /**
104
+ * Метод для открытия второго web приложения в рамках
105
+ * одной вебвью сессии
106
+ * сохраняет все текущее состояние текущего экземпляра bridgeToAm и AmNavigationAndTitle в sessionStorage, а
107
+ * так же наполняет url необходимыми query параметрами. Работает только в Android окружении.
108
+ * В IOS окружении будет открыто новое webview поверх текущего.
109
+ * @param url адрес второго web приложения к которому перед переходом на него будут добавлены
110
+ * все initial query параметры от натива и параметр nextPageId (Android)
111
+ */
112
+ navigateInsideASharedSession(url) {
113
+ if (this.b2n.environment === 'ios') {
114
+ this.b2n.nativeFallbacks.visitExternalResource(url);
115
+ return;
116
+ }
117
+ // В b2n этот метод отмечен модификатором доступа private, но тут его нужно вызвать
118
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
119
+ // @ts-ignore
120
+ this.b2n.saveCurrentState();
121
+ window.location.assign(this.prepareExternalLinkBeforeOpen(url));
122
+ }
123
+ /**
124
+ * ПОКА НЕ ИСПОЛЬЗОВАТЬ В ПРОДЕ (МЕТОД НЕ РАБОТАЕТ КОРРЕКТНО НА ANDROID)
125
+ * НА ANDROID ПРИ ПЕРЕЗАГРУЗКЕ СТРАНИЦЫ В ИСТРИЮ ЛОЖИТСЯ + ЕЩЕ ОДИН ЛИШНИЙ ЭЛЕМЕНТ
126
+ * Данный метод будет дорабатываться отдельной задачей
127
+ * Безопасный способ для перезагрузки страницы.
128
+ * Производит предварительное сохранение текущего состояния
129
+ * BridgeToNative и NativeNavigationAndTitle перед вызовом location.reload()
130
+ */
131
+ reloadPage() {
132
+ // В b2n этот метод отмечен модификатором доступа private, но тут его нужно вызвать
133
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
134
+ // @ts-ignore
135
+ this.b2n.saveCurrentState();
136
+ window.location.reload();
137
+ }
138
+ /**
139
+ * Метод для сохранения текущего состояния NativeNavigationAndTitle в sessionStorage.
140
+ */
141
+ saveCurrentState() {
142
+ const currentState = {
143
+ title: this.nativeHistoryStack[this.nativeHistoryStack.length - 1],
144
+ nativeHistoryStack: this.nativeHistoryStack,
145
+ };
146
+ sessionStorage.setItem(constants_1.PREVIOUS_NATIVE_NAVIGATION_AND_TITLE_STATE_STORAGE_KEY, JSON.stringify(currentState));
147
+ }
148
+ /**
149
+ * Метод, вычисляющий `pageId`, который нужно послать в приложение
150
+ * для правильной синхронизации с нативной-кнопкой "Назад".
151
+ *
152
+ * @param purpose Цель взаимодействия с приложением.
153
+ * @returns Правильный pageId.
154
+ */
155
+ getNativePageId(purpose) {
156
+ function assertUnreachable(val) {
157
+ throw new Error(`Unexpected value "${val}"`);
158
+ }
159
+ let pageId;
160
+ switch (purpose) {
161
+ case 'initialization':
162
+ pageId = this.getNativePageIdForInitialization();
163
+ break;
164
+ case 'navigation':
165
+ pageId = this.getNativePageIdForNavigation();
166
+ break;
167
+ case 'title-replacing':
168
+ pageId = this.getNativePageIdForTitleReplacing();
169
+ break;
170
+ default:
171
+ assertUnreachable(purpose);
172
+ }
173
+ return pageId;
174
+ }
175
+ /**
176
+ * Вспомогательный метод для `getNativePageId` initialization кейса.
177
+ *
178
+ * @returns Правильный pageId.
179
+ */
180
+ getNativePageIdForInitialization() {
181
+ // * В iOS для "первой" страницы не нужно слать `pageId`.
182
+ // * В Android важно, чтобы `pageId` "первой" страницы
183
+ // всегда был одинаковый.
184
+ return this.b2n.environment === 'ios' ? null : 1;
185
+ }
186
+ /**
187
+ * Вспомогательный метод для `getNativePageId` navigation кейса.
188
+ *
189
+ * @returns Правильный pageId.
190
+ */
191
+ getNativePageIdForNavigation() {
192
+ const stackSize = this.nativeHistoryStack.length;
193
+ // Нажимая на кнопку назад, можно дойти до "первой" страницы,
194
+ // в iOS для "первой" страницы не нужно слать `pageId`
195
+ return this.b2n.environment === 'ios' && stackSize <= 1 ? null : stackSize;
196
+ }
197
+ /**
198
+ * Вспомогательный метод для `getNativePageId` only-title кейса.
199
+ *
200
+ * @returns Правильный pageId.
201
+ */
202
+ getNativePageIdForTitleReplacing() {
203
+ const stackSize = this.nativeHistoryStack.length;
204
+ if (this.b2n.environment === 'android') {
205
+ // Для смены заголовка в Андроид просто повторяем текущий `pageId`.
206
+ // В отличии от iOS, если не послать `pageId` первой страницы,
207
+ // Вебвью не будет закрываться по клику на нативный «Назад».
208
+ return stackSize <= 1 ? 1 : stackSize;
209
+ }
210
+ // Если в iOS не послать `pageId`, следующее нажатие на
211
+ // нативную кнопку назад закроет webview.
212
+ return stackSize <= 1 ? null : stackSize;
213
+ }
214
+ /**
215
+ * Обработчик для `window.onpopstate` события. Который сработает
216
+ * после нажатия на кнопку "Назад" в нативе, вызова `history.back()` и `history.go(-x)`.
217
+ */
218
+ handleBack() {
219
+ const previousState = !!sessionStorage.getItem(constants_1.PREVIOUS_NATIVE_NAVIGATION_AND_TITLE_STATE_STORAGE_KEY);
220
+ if (previousState) {
221
+ // В b2n этот метод отмечен модификатором доступа private дабы не торчал наружу, но тут его нужно вызвать
222
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
223
+ // @ts-ignore
224
+ this.b2n.restorePreviousState();
225
+ }
226
+ this.nativeHistoryStack = this.nativeHistoryStack.slice(0, -this.numOfBackSteps);
227
+ this.numOfBackSteps = 1;
228
+ if (this.nativeHistoryStack.length < 1) {
229
+ this.b2n.closeWebview();
230
+ return;
231
+ }
232
+ const pageTitle = this.nativeHistoryStack[this.nativeHistoryStack.length - 1];
233
+ this.syncHistoryWithNative(pageTitle, 'navigation');
234
+ }
235
+ /**
236
+ * Синхронизирует состояние истории переходов и заголовок с приложением.
237
+ *
238
+ * @param pageTitle Заголовок, который нужно отрисовать в приложении.
239
+ * @param purpose Цель взаимодействия с приложением.
240
+ */
241
+ syncHistoryWithNative(pageTitle, purpose) {
242
+ var _a;
243
+ const pageId = this.getNativePageId(purpose);
244
+ if (this.b2n.environment === 'android') {
245
+ const pageSettingsObj = { pageTitle };
246
+ if (pageId) {
247
+ pageSettingsObj.pageId = pageId;
248
+ }
249
+ const paramsToSend = JSON.stringify(pageSettingsObj);
250
+ if (this.lastSetPageSettingsParams !== paramsToSend) {
251
+ (_a = this.b2n.AndroidBridge) === null || _a === void 0 ? void 0 : _a.setPageSettings(paramsToSend);
252
+ this.lastSetPageSettingsParams = paramsToSend;
253
+ }
254
+ }
255
+ else {
256
+ const pageTitleStr = `?pageTitle=${encodeURIComponent(pageTitle)}`;
257
+ const pageIdStr = pageId ? `&pageId=${pageId}` : '';
258
+ const paramsToSend = `ios:setPageSettings/${pageTitleStr + pageIdStr}`;
259
+ if (this.lastSetPageSettingsParams !== paramsToSend) {
260
+ window.location.replace(paramsToSend);
261
+ this.lastSetPageSettingsParams = paramsToSend;
262
+ }
263
+ }
264
+ }
265
+ /**
266
+ * Метод для перехода в веб из другого веб приложения в рамках
267
+ * одной вебвью сессии
268
+ * @param pageId - Номер текущего page который нужно отправить в приложение
269
+ * @param title - Title текущего page который нужно отправить в приложение
270
+ */
271
+ supportSharedSession(pageId, title) {
272
+ this.nativeHistoryStack = new Array(pageId).fill('');
273
+ this.syncHistoryWithNative(title, 'title-replacing');
274
+ this.reassignPopstateListener();
275
+ }
276
+ /**
277
+ * Восстанавливает свое предыдущее состояние nativeHistoryStack и title из sessionStorage
278
+ */
279
+ restorePreviousState() {
280
+ const previousState = JSON.parse(sessionStorage.getItem(constants_1.PREVIOUS_NATIVE_NAVIGATION_AND_TITLE_STATE_STORAGE_KEY) || '');
281
+ this.nativeHistoryStack = previousState.nativeHistoryStack;
282
+ this.syncHistoryWithNative(previousState.title, 'title-replacing');
283
+ this.reassignPopstateListener();
284
+ sessionStorage.removeItem(constants_1.PREVIOUS_NATIVE_NAVIGATION_AND_TITLE_STATE_STORAGE_KEY);
285
+ }
286
+ /**
287
+ * Вспомогательный метод для setInitialView, supportSharedSession
288
+ * переназначает обработчик @handleBack для `window.onpopstate` события
289
+ */
290
+ reassignPopstateListener() {
291
+ window.removeEventListener('popstate', this.handleBack);
292
+ window.addEventListener('popstate', this.handleBack);
293
+ }
294
+ /**
295
+ * Вспомогательный метод для navigateInsideASharedSession.
296
+ * Подготавливает внешнюю ссылку в рамках контракта для совместной работы веб-приложений в
297
+ * рамках одной вебвью сессии
298
+ * @param url - url иного веб приложения
299
+ * @return подготовленная согласно контракту ссылка на иное веб приложение с initial query
300
+ * параметрами от нативе, а так же nextPageId
301
+ */
302
+ prepareExternalLinkBeforeOpen(url) {
303
+ const currentPageId = this.nativeHistoryStack.length;
304
+ const divider = new URL(url).searchParams.toString() ? '&' : '?';
305
+ const link = new URL(`${url}${divider}${this.b2n.originalWebviewParams}`);
306
+ link.searchParams.set('nextPageId', (currentPageId + 1).toString());
307
+ return link.toString();
308
+ }
309
+ }
310
+ exports.NativeNavigationAndTitle = NativeNavigationAndTitle;
package/package.json ADDED
@@ -0,0 +1,107 @@
1
+ {
2
+ "license": "UNLICENSED",
3
+ "name": "@alfalab/bridge-to-native",
4
+ "version": "0.0.2-beta-1c5826f",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/core-ds/bridge-to-native.git"
8
+ },
9
+ "bugs": {
10
+ "url": "https://github.com/core-ds/bridge-to-native/issues"
11
+ },
12
+ "description": "Утилита для удобной работы веб приложения внутри нативного приложения и коммуникации с ним.",
13
+ "scripts": {
14
+ "build": "yarn compile",
15
+ "changelog": "bash bin/fill-changelog-file.sh",
16
+ "compile": "yarn compile:clean && yarn compile:ts && yarn compile:copy-resources",
17
+ "compile:copy-package-json": "shx cp package.json .publish/package.json",
18
+ "compile:copy-resources": "yarn copyfiles -e \"**/*.{[jt]s*(x),snap}\" -e \"**/*.json\" -e \"src/mock/**/*\" -u 1 \"src/**/*\" .publish",
19
+ "compile:clean": "shx rm -rf .publish",
20
+ "compile:ts": "tsc --project tsconfig.build.json",
21
+ "create-release-commit": "git add package.json CHANGELOG.md && git commit -m \"$MESSAGE\" && git push origin \"$BRANCH\"",
22
+ "lint:scripts": "eslint \"**/*.{js,jsx,ts,tsx}\" --ext .js,.jsx,.ts,.tsx",
23
+ "lint": "yarn lint:scripts && prettier --check \"./src/*.{ts,tsx,js,jsx,json}\"",
24
+ "lint:fix": "yarn lint:scripts --fix",
25
+ "pub": "npm publish .publish --userconfig \"../.npmrc\" --tag \"$TAG\"",
26
+ "format": "prettier --write \"./**/*.{ts,tsx,js,jsx,css,json}\"",
27
+ "test": "jest --silent --collect-coverage",
28
+ "release": "yarn compile && npm version \"$VERSION\" --no-git-tag-version && yarn compile:copy-package-json && yarn pub"
29
+ },
30
+ "dependencies": {},
31
+ "devDependencies": {
32
+ "@types/jest": "27.5.2",
33
+ "@types/node": "14.14.22",
34
+ "@typescript-eslint/eslint-plugin": "^5.30.6",
35
+ "@typescript-eslint/parser": "^5.30.6",
36
+ "arui-presets-lint": "6.2.0",
37
+ "arui-scripts": "15.10.3",
38
+ "copyfiles": "2.4.1",
39
+ "eslint": "^8.20.0",
40
+ "eslint-config-airbnb": "^19.0.4",
41
+ "eslint-config-airbnb-typescript": "^17.0.0",
42
+ "eslint-config-prettier": "^8.5.0",
43
+ "eslint-import-resolver-typescript": "~3.1.5",
44
+ "eslint-plugin-cypress": "^2.12.1",
45
+ "eslint-plugin-dirnames": "^1.0.3",
46
+ "eslint-plugin-import": "^2.26.0",
47
+ "eslint-plugin-jsx-a11y": "^6.6.1",
48
+ "eslint-plugin-react": "^7.30.1",
49
+ "eslint-plugin-react-hooks": "^4.6.0",
50
+ "eslint-plugin-simple-import-sort": "^7.0.0",
51
+ "eslint-plugin-unicorn": "^42.0.0",
52
+ "husky": "^4.3.8",
53
+ "jest": "27.5.1",
54
+ "jest-junit": "10.0.0",
55
+ "lint-staged": "^12.5.0",
56
+ "prettier": "^2.7.1",
57
+ "promisify-child-process": "4.1.1",
58
+ "stylelint": "^14.9.1",
59
+ "stylelint-config-prettier": "^9.0.3",
60
+ "ts-jest": "25.3.1",
61
+ "typescript": "4.5.5",
62
+ "shx": "0.3.4"
63
+ },
64
+ "peerDependencies": {},
65
+ "jest": {
66
+ "preset": "arui-scripts",
67
+ "testEnvironment": "jsdom",
68
+ "testRegex": "/test/.*\\.ts$",
69
+ "moduleFileExtensions": [
70
+ "ts",
71
+ "js"
72
+ ],
73
+ "coveragePathIgnorePatterns": [
74
+ "/node_modules/",
75
+ "/test/"
76
+ ],
77
+ "transformIgnorePatterns": [
78
+ "node_modules/(?!(uuid)/)"
79
+ ],
80
+ "testPathIgnorePatterns": [
81
+ "/node_modules/"
82
+ ],
83
+ "reporters": [
84
+ "jest-junit",
85
+ "default"
86
+ ]
87
+ },
88
+ "jest-junit": {
89
+ "output": "./test-results.xml"
90
+ },
91
+ "prettier": "arui-presets-lint/prettier",
92
+ "eslintConfig": {
93
+ "extends": "./node_modules/arui-presets-lint/eslint"
94
+ },
95
+ "stylelint": {
96
+ "extends": "arui-presets-lint/stylelint",
97
+ "ignoreFiles": [
98
+ "coverage/**/*.js"
99
+ ]
100
+ },
101
+ "commitlint": {
102
+ "extends": "./node_modules/arui-presets-lint/commitlint"
103
+ },
104
+ "publishConfig": {
105
+ "registry": "https://registry.npmjs.org"
106
+ }
107
+ }
package/types.d.ts ADDED
@@ -0,0 +1,35 @@
1
+ export declare type NativeParams = {
2
+ appVersion: string;
3
+ title?: string;
4
+ iosAppId?: string;
5
+ theme: string;
6
+ nextPageId: number | null;
7
+ originalWebviewParams: string;
8
+ };
9
+ declare type NativeFeatureFtKey = 'linksInBrowserAndroid' | 'linksInBrowserIos';
10
+ export declare type NativeFeaturesFts = Record<NativeFeatureFtKey, boolean>;
11
+ export declare type NativeFeatureKey = 'geolocation' | 'linksInBrowser';
12
+ declare type NativeFeaturesParams = Readonly<Record<NativeFeatureKey, {
13
+ nativeFeatureFtKey?: NativeFeatureFtKey;
14
+ fromVersion: string;
15
+ }>>;
16
+ export declare type NativeFeaturesFromVersion = Readonly<{
17
+ android: NativeFeaturesParams;
18
+ ios: NativeFeaturesParams;
19
+ }>;
20
+ export declare type Environment = 'android' | 'ios';
21
+ export declare type WebViewWindow = Window & {
22
+ Android?: {
23
+ setPageSettings: (params: string) => void;
24
+ };
25
+ handleRedirect?: (appName: string, path?: string, params?: Record<string, string>) => VoidFunction;
26
+ };
27
+ export declare type PdfType = 'pdfFile' | 'base64' | 'binary';
28
+ export declare type PreviousBridgeToNativeState = Omit<NativeParams, 'title' | 'theme'> & {
29
+ theme: 'dark' | 'light';
30
+ };
31
+ export declare type PreviousNativeNavigationAndTitleState = {
32
+ nativeHistoryStack: string[];
33
+ title: string;
34
+ };
35
+ export {};
package/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/utils.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Разделяет веб ссылку на компоненты
3
+ * @param route внутренний путь для навигации
4
+ * @return объект с appName, route, query
5
+ */
6
+ export declare const extractAppNameRouteAndQuery: (route: string) => {
7
+ appName: string;
8
+ path: string;
9
+ query: Record<string, string> | undefined;
10
+ };
11
+ /**
12
+ * Возвращает экземпляр `URL` из ссылки, докидывая `https://` при отсутствии.
13
+ */
14
+ export declare const getUrlInstance: (link: string) => URL;
15
+ /**
16
+ * Проверяет, что переданная строка содержит версию приложения в правильном формате.
17
+ *
18
+ * @param version Строка с версией для проверки.
19
+ * @returns Правильный формат или нет.
20
+ */
21
+ export declare const isValidVersionFormat: (version?: string | undefined) => boolean;
package/utils.js ADDED
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isValidVersionFormat = exports.getUrlInstance = exports.extractAppNameRouteAndQuery = void 0;
4
+ /**
5
+ * Разделяет веб ссылку на компоненты
6
+ * @param route внутренний путь для навигации
7
+ * @return объект с appName, route, query
8
+ */
9
+ const extractAppNameRouteAndQuery = (route) => {
10
+ var _a;
11
+ let appName = '';
12
+ let path = '';
13
+ let query;
14
+ const clearedPath = route.replace(/(?:^\/)|(?:\/$)/g, '');
15
+ const segments = clearedPath.split('/');
16
+ const queryByPath = clearedPath.split('?')[1];
17
+ appName = ((_a = segments.shift()) === null || _a === void 0 ? void 0 : _a.split('?')[0]) || '';
18
+ if (queryByPath) {
19
+ query = Array.from(new URLSearchParams(queryByPath).entries()).reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {});
20
+ }
21
+ path = segments.join('/').replace(`?${queryByPath}`, '');
22
+ return { appName, path, query };
23
+ };
24
+ exports.extractAppNameRouteAndQuery = extractAppNameRouteAndQuery;
25
+ /**
26
+ * Возвращает экземпляр `URL` из ссылки, докидывая `https://` при отсутствии.
27
+ */
28
+ const getUrlInstance = (link) => {
29
+ const protocolRequiredPattern = /^https?:\/\//;
30
+ let url;
31
+ if (protocolRequiredPattern.test(link)) {
32
+ url = new URL(link);
33
+ }
34
+ else {
35
+ try {
36
+ // Пробуем докинуть `https://`, как правило, это помогает.
37
+ url = new URL(`https://${link}`);
38
+ }
39
+ catch (e) {
40
+ // Кажется, добавив протокол, сюда мы больше не сможем вывалиться, но на всякий случай...
41
+ url = new URL('about:blank');
42
+ }
43
+ }
44
+ return url;
45
+ };
46
+ exports.getUrlInstance = getUrlInstance;
47
+ /**
48
+ * Проверяет, что переданная строка содержит версию приложения в правильном формате.
49
+ *
50
+ * @param version Строка с версией для проверки.
51
+ * @returns Правильный формат или нет.
52
+ */
53
+ const isValidVersionFormat = (version) => {
54
+ if (!version)
55
+ return false;
56
+ const versionPattern = /^\d+\.\d+\.\d+$/;
57
+ return versionPattern.test(version);
58
+ };
59
+ exports.isValidVersionFormat = isValidVersionFormat;