Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Синхронизация b2am => b2n #15

Merged
merged 6 commits into from
Feb 20, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"changelog": "bash bin/fill-changelog-file-and-notify-github.sh",
"compile": "yarn compile:clean && yarn compile:ts && yarn compile:copy-resources",
"compile:copy-package-json": "shx cp package.json .publish/package.json",
"compile:copy-resources": "yarn copyfiles -e \"**/*.{[jt]s*(x),snap}\" -e \"**/*.json\" -e \"src/mock/**/*\" -u 1 \"src/**/*\" .publish",
"compile:copy-resources": "yarn copyfiles -e \"**/*.{[jt]s*(x),snap}\" -e \"**/*.json\" -u 1 \"src/**/*\" .publish",
"compile:clean": "shx rm -rf .publish",
"compile:ts": "tsc --project tsconfig.build.json",
"lint:scripts": "eslint \"**/*.{js,jsx,ts,tsx}\" --ext .js,.jsx,.ts,.tsx",
Expand Down Expand Up @@ -67,13 +67,15 @@
],
"coveragePathIgnorePatterns": [
"/node_modules/",
"/test/"
"/test/",
"/src/index.ts"
],
"transformIgnorePatterns": [
"node_modules/(?!(uuid)/)"
],
"testPathIgnorePatterns": [
"/node_modules/"
"/node_modules/",
"/test/mock"
],
"reporters": [
"jest-junit",
Expand Down
228 changes: 228 additions & 0 deletions src/bridge-to-native.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
/* eslint-disable no-underscore-dangle */

import {
CLOSE_WEBVIEW_SEARCH_KEY,
CLOSE_WEBVIEW_SEARCH_VALUE,
nativeFeaturesFromVersion,
PREVIOUS_B2N_STATE_STORAGE_KEY,
versionToIosAppId,
} from './constants';
import { NativeFallbacks } from './native-fallbacks';
import { NativeNavigationAndTitle } from './native-navigation-and-title';
import type {
Environment,
HandleRedirect,
NativeFeatureKey,
NativeParams,
Theme,
WebViewWindow,
} from './types';
import { PreviousBridgeToNativeState } from './types';
import { isValidVersionFormat } from './utils';

/**
* Этот класс — абстракция для связи веб приложения с нативом и предназначен ТОЛЬКО
* для использования в вебвью окружении.
*/
export class BridgeToNative {
// Webview, запущенное в Android окружении имеет объект `Android` в window.
public readonly AndroidBridge = (window as WebViewWindow).Android;

public readonly environment: Environment = this.AndroidBridge ? 'android' : 'ios';

public readonly nativeFallbacks: NativeFallbacks;

private nextPageId: number | null;

private _nativeNavigationAndTitle: NativeNavigationAndTitle;

private _originalWebviewParams: string;

// В формате `x.x.x`.
private _appVersion: string;

// Необходимо для формирования диплинка.
private _iosAppId?: string;

private _theme: Theme;

private readonly _blankPagePath: string;

private readonly _handleRedirect: HandleRedirect;

constructor(
handleRedirect: HandleRedirect,
blankPagePath: string,
nativeParams?: NativeParams,
) {
const previousState = !!sessionStorage.getItem(PREVIOUS_B2N_STATE_STORAGE_KEY);

if (previousState) {
this.restorePreviousState();
this.nativeFallbacks = new NativeFallbacks(this);
this._blankPagePath = blankPagePath;

return;
}

this._appVersion =
nativeParams && isValidVersionFormat(nativeParams?.appVersion)
? nativeParams.appVersion
: '0.0.0';
this._iosAppId = this.getIosAppId(nativeParams?.iosAppId);
this._theme = nativeParams?.theme === 'dark' ? 'dark' : 'light';
this._originalWebviewParams = nativeParams?.originalWebviewParams || '';
this._nativeNavigationAndTitle = new NativeNavigationAndTitle(
this,
nativeParams ? nativeParams.nextPageId : null,
nativeParams?.title,
handleRedirect,
);
this._handleRedirect = handleRedirect;

this.nextPageId = nativeParams ? nativeParams.nextPageId : null;
this.nativeFallbacks = new NativeFallbacks(this);
this._blankPagePath = blankPagePath;
}

get theme() {
return this._theme;
}

get appVersion() {
return this._appVersion;
}

get iosAppId() {
return this._iosAppId;
}

get nativeNavigationAndTitle() {
return this._nativeNavigationAndTitle;
}

get originalWebviewParams() {
return this._originalWebviewParams;
}

/**
* Метод, проверяющий, можно ли использовать нативную функциональность в текущей версии приложения.
*
* @param feature Название функциональности, которую нужно проверить.
*/
public canUseNativeFeature(feature: NativeFeatureKey) {
const { fromVersion } = nativeFeaturesFromVersion[this.environment][feature];

return this.isCurrentVersionHigherOrEqual(fromVersion);
}

/**
* Метод, отправляющий сигнал нативу, что нужно закрыть текущее вебвью.
*/
// eslint-disable-next-line class-methods-use-this
public closeWebview() {
const originalPageUrl = new URL(window.location.href);

originalPageUrl.searchParams.set(CLOSE_WEBVIEW_SEARCH_KEY, CLOSE_WEBVIEW_SEARCH_VALUE);
window.location.href = originalPageUrl.toString();
}

/**
* Сравнивает текущую версию приложения с переданной.
*
* @param versionToCompare Версия, с которой нужно сравнить текущую.
* @returns `true` – текущая версия больше или равняется переданной,
* `false` – текущая версия ниже.
*/
public isCurrentVersionHigherOrEqual(versionToCompare: string) {
if (!isValidVersionFormat(versionToCompare)) {
return false;
}

const matchPattern = /(\d+)\.(\d+)\.(\d+)/;

type ExpectedTupple = [string, string, string, string];

const [, ...appVersionComponents] = this._appVersion.match(matchPattern) as ExpectedTupple; // Формат версии проверен в конструкторе, можно смело убирать `null` из типа.

const [, ...versionToCompareComponents] = versionToCompare.match(
matchPattern,
) as ExpectedTupple;

for (let i = 0; i < appVersionComponents.length; i++) {
if (appVersionComponents[i] !== versionToCompareComponents[i]) {
return appVersionComponents[i] >= versionToCompareComponents[i];
}
}

return true;
}

/**
* Сохраняет текущее состояние BridgeToNative в sessionStorage.
* Так же сохраняет текущее состояние nativeNavigationAndTitle.
*/
private saveCurrentState() {
// В nativeNavigationAndTitle этот метод отмечен модификатором доступа private дабы не торчал наружу, но тут его нужно вызвать
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this._nativeNavigationAndTitle.saveCurrentState();

const currentState: PreviousBridgeToNativeState = {
appVersion: this._appVersion,
theme: this._theme,
nextPageId: this.nextPageId,
originalWebviewParams: this._originalWebviewParams || '',
iosAppId: this._iosAppId,
};

sessionStorage.setItem(PREVIOUS_B2N_STATE_STORAGE_KEY, JSON.stringify(currentState));
}

/**
* Возвращает схему приложения в iOS окружении, на основе версии.
*
* @param knownIosAppId Тип iOS приложения, если он известен.
* @returns Тип приложения, `undefined` для Android окружения.
*/
private getIosAppId(knownIosAppId?: string) {
if (this.environment !== 'ios') {
return undefined;
}

if (knownIosAppId) {
return knownIosAppId;
}

const keys = Object.keys(versionToIosAppId);

const rightKey =
[...keys].reverse().find((version) => this.isCurrentVersionHigherOrEqual(version)) ||
keys[0];

return atob(versionToIosAppId[rightKey as keyof typeof versionToIosAppId]);
}

/**
* Восстанавливает свое предыдущее состояние из sessionStorage
*/
private restorePreviousState() {
const previousState: PreviousBridgeToNativeState = JSON.parse(
sessionStorage.getItem(PREVIOUS_B2N_STATE_STORAGE_KEY) || '',
);

this._appVersion = previousState.appVersion;
this._iosAppId = previousState.iosAppId;
this._theme = previousState.theme;
this._originalWebviewParams = previousState.originalWebviewParams;
this.nextPageId = previousState.nextPageId;
this._nativeNavigationAndTitle = new NativeNavigationAndTitle(
this,
previousState.nextPageId,
'',
this._handleRedirect,
);

sessionStorage.removeItem(PREVIOUS_B2N_STATE_STORAGE_KEY);
}
}
6 changes: 3 additions & 3 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ export const PREVIOUS_B2N_STATE_STORAGE_KEY = 'previousBridgeToNativeState';
export const PREVIOUS_NATIVE_NAVIGATION_AND_TITLE_STATE_STORAGE_KEY =
'previousNativeNavigationAndTitleState';


export const versionToIosAppId = {
'0.0.0': 'YWxmYWJhbms=',
'12.22.0': 'YWNvbmNpZXJnZQ==',
Expand All @@ -17,16 +16,17 @@ export const versionToIosAppId = {
export const nativeFeaturesFromVersion: NativeFeaturesFromVersion = {
android: {
linksInBrowser: {
nativeFeatureFtKey: 'linksInBrowserAndroid',
fromVersion: '11.71.0',
},
geolocation: { fromVersion: '11.71.0' },
},
ios: {
linksInBrowser: {
nativeFeatureFtKey: 'linksInBrowserIos',
fromVersion: '13.3.0',
},
geolocation: { fromVersion: '0.0.0' },
},
} as const;

export const DEEP_LINK_PATTERN =
puptup marked this conversation as resolved.
Show resolved Hide resolved
/^(\/|\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\/)/;
Loading
Loading