@sberid/js-sdk
Version:
Javascript SDK для партнеров Сбер ID, упрощающая подключение SberbankID на сайте.
196 lines (168 loc) • 7.3 kB
text/typescript
import {OidcParams} from '../interfaces/common';
import {AddionalRedirectParams, UniversalLinkConfig, UniversalLinkResponse} from './interfaces';
import {AbstractParser, BrowserMode} from '../browser/interfaces';
import {buildUrl, getUrlSearchParams, log} from '../utils/common';
import {getBrowserAlias} from './utils';
import {BrowserModeDetector, Browser} from '../browser/';
import {BrowserName, MAX_STATE_LENGTH} from '../constants/common';
import {BROWSERS, Os, Protocol, appRedirects, BROWSER_ALIASES_MAP} from './constants';
import {defaultUniversalLinkConfig} from '../sberid-sdk/constants';
import {generateRandom, isOIDCRedirect} from '../sberid-sdk/utils';
import {SberidSDKProps} from '../sberid-sdk';
import {Cookies} from '../services/cookies';
export class SberidUniversalLink {
private config: UniversalLinkConfig;
proxyParams: string[] = ['authApp', 'app_redirect_uri'];
parser: AbstractParser = Browser.getParser(window.navigator.userAgent);
constructor(config: SberidSDKProps) {
const browserName = this.parser.getBrowserName();
const display =
config.display && browserName !== BrowserName.INTERNET_EXPLORER
? config.display
: 'page';
this.config = {
...defaultUniversalLinkConfig,
...config.universalLink,
debug: !!config.debug,
generateState: !!config.generateState,
display,
};
if (!config.utmProxyDisabled) {
this.proxyParams = this.proxyParams.concat([
'utm_source',
'utm_medium',
'utm_campaign',
'utm_term',
'utm_content',
]);
}
if (this.config.debug) {
log(['Инициализируем модуль UniversalLink'], 'info');
log(['Параметры инициализации: ', this.config], 'info');
}
}
isAllowedBrowser(alias: string): boolean {
return BROWSERS.includes(alias);
}
getAdditionalRedirectParams(
os: string,
browser: string,
redirect_uri: string,
): AddionalRedirectParams {
const params: AddionalRedirectParams = {};
if (
this.config.needAdditionalRedirect &&
Object.prototype.hasOwnProperty.call(appRedirects, os) &&
Object.prototype.hasOwnProperty.call(appRedirects[<Os>os], browser)
) {
switch (os) {
case Os.ANDROID:
params.package = appRedirects[os][browser];
break;
case Os.IOS:
let redirectUri = '';
try {
redirectUri = decodeURIComponent(redirect_uri || '');
} catch (e) {
log(['Ошибка декодирования redirect_uri', e], 'error');
}
const protocol = redirectUri.includes(`${Protocol.HTTPS}://`)
? Protocol.HTTPS
: Protocol.HTTP;
params.ext_redirect_uri = `${
appRedirects[os][browser][protocol]
}${redirectUri.replace(/^https?:\/\//, '')}`;
break;
}
}
return params;
}
generateState(length = MAX_STATE_LENGTH): string {
return generateRandom(length);
}
buildOidcParams(oidcParams: OidcParams): OidcParams {
const params: Record<string, string> = {};
for (const key in oidcParams) {
if (Object.prototype.hasOwnProperty.call(oidcParams, key)) {
try {
params[key] = decodeURIComponent(oidcParams[key]);
} catch (e) {
params[key] = '';
if (this.config.debug) {
log(['Ошибка декодирования oidc параметра', e], 'error');
}
}
}
}
if (oidcParams.scope && oidcParams.scope.includes('+')) {
oidcParams.scope = oidcParams.scope.split('+').join(' ');
}
const alias = getBrowserAlias(this.parser.getBrowserName());
const os = this.parser.getOSName(true);
const customParams = {
...this.getAdditionalRedirectParams(os, alias, params.redirect_uri),
} as OidcParams;
customParams.display = this.config.display || 'page';
const searchParams = getUrlSearchParams();
const searchParamKeys = Object.keys(searchParams);
for (let i = 0; i < searchParamKeys.length; i += 1) {
if (this.proxyParams.includes(searchParamKeys[i])) {
customParams[searchParamKeys[i]] = searchParams[searchParamKeys[i]];
}
}
if (this.config.generateState && this.config.display === 'popup' && !isOIDCRedirect()) {
const state = this.generateState();
Cookies.set('sbid_state', state, {
path: '/',
});
customParams.state = state;
}
return {...params, ...customParams};
}
async run(params = {} as OidcParams): Promise<UniversalLinkResponse> {
const oidcParams = {
...this.config.params,
...params,
};
const isPrivate = (await this.detect()) === 'incognito';
const result = this.parser.getResult();
const alias = getBrowserAlias(this.parser.getBrowserName());
const os = this.parser.getOSName(true);
const oidc = this.buildOidcParams(oidcParams);
const isAllowedAndroidBrowser = os === Os.ANDROID && this.isAllowedBrowser(alias);
const isAllowedIosBrowser =
os === Os.IOS &&
(alias === BROWSER_ALIASES_MAP[BrowserName.SAFARI] ||
(this.isAllowedBrowser(alias) && this.config.needAdditionalRedirect));
const isUniversalLink =
!isPrivate && !result.app.name && (isAllowedIosBrowser || isAllowedAndroidBrowser);
const deeplink = buildUrl(this.config.deeplinkUrl, params);
const link = buildUrl(
isUniversalLink ? this.config.universalLinkUrl : this.config.baseUrl,
params,
);
const response: UniversalLinkResponse = {
isPrivate,
isUniversalLink,
app: result.app.name,
isWebview: !!result.app.name,
os,
browser: alias,
link: link,
deeplink: deeplink,
oidc,
};
if (this.config.debug) {
log(['Получены данные для формирования ссылки: ', response]);
}
return Promise.resolve(response);
}
detect(): Promise<BrowserMode> {
try {
const browsingModeDetector = new BrowserModeDetector();
return browsingModeDetector.run();
} catch (e) {
return Promise.resolve('normal');
}
}
}