Skip to content

Commit

Permalink
feat(*): fixes after review
Browse files Browse the repository at this point in the history
  • Loading branch information
Andrei Takarski committed Nov 28, 2024
1 parent 7e9f88e commit aaffe4d
Show file tree
Hide file tree
Showing 20 changed files with 523 additions and 655 deletions.
3 changes: 3 additions & 0 deletions src/client/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { BridgeToNative } from './bridge-to-native';
export { NativeParams, Theme, Environment, NativeFeatureKey, PdfType } from './types';
export { getNativeParamsFromCookies } from './utils';
13 changes: 3 additions & 10 deletions src/client/types.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
export type NativeParams = {
appVersion: string;
title?: string;
// В ранних версиях iOS приложение не пробрасывет схему приложения в URL в прод окружении.
// Для таких версий есть мэппинг `./constants` → `versionToIosAppId`.
iosAppId?: string;
theme: string;
nextPageId: number | null;
originalWebviewParams: string;
};
import { NativeParamsType } from "../shared/types";

export type NativeParams = NativeParamsType;

export type NativeFeatureKey =
// Возможность работы с геолокацией.
Expand Down
6 changes: 6 additions & 0 deletions src/client/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Environment } from './types';
import { ANDROID_APP_ID } from './constants';
import { extractNativeParamsFromCookies } from "../shared/utils";

/**
* Разделяет веб ссылку на компоненты
Expand Down Expand Up @@ -76,3 +77,8 @@ export const getAppId = (environment: Environment, iosAppId?: string) => {

return null;
};

/**
* Возвращает объект с `webview-параметрами` из cookies
*/
export const getNativeParamsFromCookies = (): Record<string, unknown> | null => extractNativeParamsFromCookies(document.cookie);
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export { BridgeToNative } from './client/bridge-to-native';
export { NativeParams, Theme, Environment, NativeFeatureKey, PdfType } from './client/types';
export * from './client'
export * from './server';
6 changes: 0 additions & 6 deletions src/server/constants.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
export const THEME_QUERY = 'theme';
export const TITLE = 'title';

export const WEBVIEW_IOS_APP_ID_QUERY = 'applicationId';
export const WEBVIEW_IOS_APP_VERSION_QUERY = 'device_app_version';
export const WEBVIEW_WITHOUT_LAYOUT_QUERY = 'without_layout';
export const WEBVIEW_NEXT_PAGE_ID_QUERY = 'nextPageId';

export const CAPSULE_UA_SUBSTR = 'Capsule';
export const AKEY_UA_SUBSTR = 'AKEY';
export const VOSKHOD_UA_SUBSTR = 'VOSKHOD';

export const NATIVE_PARAMS_COOKIE_NAME = 'app_native_params';
4 changes: 2 additions & 2 deletions src/server/extract-and-join-original-webview-params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ const webviewInitParamsDictionary = [
];

/**
* Данная утилита извлекает из Fastify.Query все известные
* Данная утилита извлекает из запроса все известные
* сервисные query параметры которые добавляются к url внутри
* webview при первой инициализации и собирает их в query строку.
*
* @param query - Fastify.Query в формате объекта
* @param query - Query в формате объекта
* @return строка query параметров в формате: "title=Title&theme=dark..."
* */
export const extractAndJoinOriginalWebviewParams = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,32 @@ import {
WEBVIEW_IOS_APP_VERSION_QUERY,
WEBVIEW_NEXT_PAGE_ID_QUERY,
WEBVIEW_WITHOUT_LAYOUT_QUERY,
NATIVE_PARAMS_COOKIE_NAME
} from './constants';

import { extractAppVersion } from './utils';

import { extractAndJoinOriginalWebviewParams } from './extract-and-join-original-webview-params';
import { checkIsWebview } from './check-is-webview';
import { iosAppIdPattern, versionPattern } from './reg-exp-patterns';
import { EmptyNativeParams, NativeParams, RequestHeaderType } from "./types";
import {isWebviewEnvironment} from "./is-webview-environment";

/**
* Вытаскивает из query и headers все детали для вебвью.
*
* @returns Примечание по `appVersion`: В вебвью окружении версия всегда имеет формат `x.x.x`.
*/

export const detectAndExtractNativeParams = (
request: RequestHeaderType,
addCookie?: (cookieKey: string, cookieValue: string) => void
): EmptyNativeParams | NativeParams => {
const isWebview = checkIsWebview(request);
export const extractNativeParams = (
request: RequestHeaderType
): NativeParams | null => {

if (!isWebview) {
return { isWebview } as EmptyNativeParams;
if(!isWebviewEnvironment(request)) {
return null;
}

const {
[THEME_QUERY]: themeQuery,
// При желании через диплинк на вебвью можно передать желаемый заголовок,
// который АО установит в верхней АМ панели при загрузке АО.
// При желании через диплинк на вебвью можно передать желаемый заголовок
// По умолчанию нужна именно пустая строка.
[TITLE]: title = '',
// Говорят, этого может и не быть в урле. Формат `com.xxxxxxxxx.app`.
Expand All @@ -57,7 +53,7 @@ export const detectAndExtractNativeParams = (
iosAppId = appIdSubsting;
}

// Определяем версию АМ из query или заголовка.
// Определяем версию приложения из query или заголовка.
let appVersion = '0.0.0';

const appVersionFromHeaders = extractAppVersion(request);
Expand All @@ -76,17 +72,13 @@ export const detectAndExtractNativeParams = (
const nativeParams = {
appVersion,
iosAppId,
isWebview,
isWebview: true,
theme: themeQuery === 'dark' ? 'dark' : 'light',
title,
withoutLayout: withoutLayoutQuery === 'true',
originalWebviewParams,
nextPageId: nextPageId ? Number(nextPageId) : null,
} as NativeParams;

if(addCookie) {
addCookie(NATIVE_PARAMS_COOKIE_NAME, encodeURIComponent(JSON.stringify(nativeParams)));
}

return nativeParams;
};
7 changes: 3 additions & 4 deletions src/server/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
export * from './detect-and-extract-native-params';
export * from './check-is-webview';
export * from './is-webview-environment';
export * from './extract-native-params';
export * from './set-native-params-cookie';
export * from './types';
export * from './reg-exp-patterns';
export * from './extract-and-join-original-webview-params';
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import {
extractAppVersion,
extractUserAgent,
isAkeyWebview,
extractNativeParamsFromCookies
extractNativeParamsFromCookieHeader
} from './utils';
import { RequestHeaderType } from './types';
import { versionPattern, webviewUaIOSPattern } from './reg-exp-patterns';
Expand All @@ -11,10 +10,6 @@ export const isWebviewByUserAgent = (
userAgent: string,
appVersion: string | undefined,
) => {
if (userAgent && isAkeyWebview(userAgent)) {
return false;
}

return (
(appVersion && versionPattern.test(appVersion)) || !!userAgent?.match(webviewUaIOSPattern)
);
Expand All @@ -23,14 +18,14 @@ export const isWebviewByUserAgent = (
export const isWebviewByCookies = (nativeParamsFromCookies: Record<string, any> | null) => {
return !!(nativeParamsFromCookies && nativeParamsFromCookies.isWebview)
}
export const checkIsWebview = (
export const isWebviewEnvironment = (
request: RequestHeaderType,
): boolean => {
const userAgent = extractUserAgent(request);

// `app-version` в заголовках – индикатор вебвью. В iOS есть только в первом запросе от webview
const appVersion = extractAppVersion(request);
const nativeParamsFromCookies = extractNativeParamsFromCookies(request);
const nativeParams = extractNativeParamsFromCookieHeader(request);

return isWebviewByCookies(nativeParamsFromCookies) || isWebviewByUserAgent(userAgent, appVersion);
return isWebviewByCookies(nativeParams) || isWebviewByUserAgent(userAgent, appVersion);
};
6 changes: 6 additions & 0 deletions src/server/set-native-params-cookie.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import {NATIVE_PARAMS_COOKIE_NAME} from "./constants";

export const setNativeParamsCookie = (params: Record<string, string>, setCookie: (name: string, value: string) => void): void => {
setCookie(NATIVE_PARAMS_COOKIE_NAME, encodeURIComponent(JSON.stringify(params)))
}

9 changes: 2 additions & 7 deletions src/server/types.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
import type { NativeParamsType } from '../shared/types';
export type RequestHeaderType = Record<string, any>;

export type EmptyNativeParams = {
isWebview: false;
};

export type NativeParams = {
appVersion: string;
iosAppId?: string;
export type NativeParams = NativeParamsType & {
isWebview: true;
theme: 'dark' | 'light';
title: string;
withoutLayout: boolean;
originalWebviewParams: string;
nextPageId: number | null;
};
37 changes: 3 additions & 34 deletions src/server/utils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
import {
CAPSULE_UA_SUBSTR,
AKEY_UA_SUBSTR,
VOSKHOD_UA_SUBSTR,
NATIVE_PARAMS_COOKIE_NAME
} from './constants';
import { RequestHeaderType } from './types';
import { extractNativeParamsFromCookies } from "../shared/utils";

/**
* Заголовок с версией приложения, который посылает вебвью из AM Android
Expand All @@ -28,32 +23,6 @@ export function extractUserAgent(request: RequestHeaderType): string {
/**
* Возвращает объект с `webview-параметрами` из cookies
*/
export function extractNativeParamsFromCookies(request: RequestHeaderType): Record<string, string> | null {
const cookieHeader = request.headers['cookie'];

if (!cookieHeader) {
return {};
}

const cookiesArray = cookieHeader.split('; ');
const cookieString = cookiesArray.find((cookie: string) => cookie.startsWith(`${NATIVE_PARAMS_COOKIE_NAME}=`));

if (!cookieString) return null;

const [, value] = cookieString.split('=');

try {
return JSON.parse(decodeURIComponent(value));
} catch {
return null;
}
export function extractNativeParamsFromCookieHeader(request: RequestHeaderType): Record<string, unknown> | null {
return extractNativeParamsFromCookies(request.headers['cookie'])
}


/**
* Проверка по юзерагенту на сервере
*/
export const isAkeyWebview = (userAgent: string) =>
userAgent.includes(CAPSULE_UA_SUBSTR) ||
userAgent.includes(AKEY_UA_SUBSTR) ||
userAgent.includes(VOSKHOD_UA_SUBSTR);
10 changes: 10 additions & 0 deletions src/shared/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export type NativeParamsType = {
appVersion: string;
title?: string;
// В ранних версиях iOS приложение не пробрасывет схему приложения в URL в прод окружении.
// Для таких версий есть мэппинг `./constants` → `versionToIosAppId`.
iosAppId?: string;
theme: string;
nextPageId: number | null;
originalWebviewParams: string;
};
24 changes: 24 additions & 0 deletions src/shared/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { NATIVE_PARAMS_COOKIE_NAME } from "../server/constants";

/**
* Возвращает объект с `webview-параметрами` из cookies
*/
export function extractNativeParamsFromCookies(cookies?: string): Record<string, unknown> | null {

if (!cookies) {
return null;
}

const cookiesArray = cookies.split('; ');
const cookieString = cookiesArray.find((cookie: string) => cookie.startsWith(`${NATIVE_PARAMS_COOKIE_NAME}=`));

if (!cookieString) return null;

const [, value] = cookieString.split('=');

try {
return JSON.parse(decodeURIComponent(value));
} catch {
return null;
}
}
Loading

0 comments on commit aaffe4d

Please sign in to comment.