UNPKG

@sberid/js-sdk

Version:

Javascript SDK для партнеров Сбер ID, упрощающая подключение SberbankID на сайте.

541 lines (447 loc) 18.3 kB
import {ERROR_MESSAGES} from './constants'; import {openDialog} from './utils'; import { SberidSDKConfig, SberidSDKErrorResult, SberidSDKSuccessResult, SberidSDKProps, } from './interfaces'; import {initSA, sendSberAnalytics} from '../sberid-analytics'; import {OidcParams, User} from '../interfaces/common'; import {buildUrl, getVersion, log, showLoader, hideLoader} from '../utils/common'; import {Browser} from '../browser'; import {Cookies} from '../services/cookies'; import {SberidUniversalLink} from '../universal-link'; import {getAuthUrl, BASE_URL, BrowserName} from '../constants/common'; import {FastLogin, AbstractFastLogin} from '../fast-login'; import {NotificationBanner} from '../notification-banner'; import {SberidButton} from '../sberid-button'; import {UserHelper} from '../helper'; import {FastAuthorizationResponse} from '../fast-login/interfaces'; export class SberidSDK { buttons: SberidButton[] = []; user: User | undefined; oidcParams!: OidcParams; config!: SberidSDKConfig; onSuccessCallback: (data?: SberidSDKSuccessResult) => void = () => {}; onErrorCallback: (data?: SberidSDKErrorResult) => void = () => {}; parser = Browser.getParser(window.navigator.userAgent); windowStatus = 'closed'; w: any; fastLogin!: AbstractFastLogin; universalLinkDetect!: SberidUniversalLink; theme: any; notifyBanner!: NotificationBanner; removeWindowListener(): void {} private userHelper: UserHelper = UserHelper.getInstance(); constructor(config: SberidSDKProps) { if (config.onSuccessCallback) { this.onSuccessCallback = config.onSuccessCallback; } if (config.onErrorCallback) { this.onErrorCallback = config.onErrorCallback; } this.updateConfig(config); this.onInit(config); } updateConfig(config: SberidSDKProps): void { const deeplinkParams = { enable: false, allowSberIDRedirects: [], mode: 'default', ...config.deeplink, }; const browserName = this.parser.getBrowserName(); const display = config.display && browserName !== BrowserName.INTERNET_EXPLORER ? config.display : 'page'; this.config = { personalization: !!config.personalization, changeUser: !!config.changeUser, baseUrl: config.baseUrl ?? BASE_URL, debug: !!config.debug, mweb2app: !!config.mweb2app, fastLogin: !!config.fastLogin?.enable, sa: !!config.sa?.enable, fastLoginMode: config.fastLogin?.mode, notification: !!config.notification?.enable, display, generateState: !!config.generateState, oidc: config.oidc, container: config.container, deeplink: deeplinkParams, }; } onInit(config: SberidSDKProps): void { if (this.config.sa) { if (config?.sa?.init === 'auto') { initSA(config); } } if (this.config.debug) { log(['Инициализируем SDK'], 'info'); log(['Параметры инициализации: ', this.config], 'info'); } this.userHelper.setConfig({ baseUrl: this.config.baseUrl, clientId: this.config.oidc.client_id, }); this.userHelper.setListener(this.handleUserChange.bind(this)); this.fastLogin = new FastLogin(config); if (this.config.notification) { this.notifyBanner = new NotificationBanner({ onButtonClick: this.handleButtonClick.bind(this), ...config, }); } this.universalLinkDetect = new SberidUniversalLink(config); if (this.config.container) { let container: HTMLDivElement[] = []; if (this.config.container instanceof HTMLDivElement) { container = container.concat(this.config.container); } else { container = Array.from( document.querySelectorAll<HTMLDivElement>(this.config.container), ); } for (let i = 0; i < container.length; i += 1) { this.createButton(config, container[i]); } if (this.config.sa) { sendSberAnalytics({ eventCategory: 'Login', eventAction: 'sberid_Login_Show', eventType: 'Show', extendedProperties: {}, }); } } this.setOIDCParams(this.config.oidc); if (this.config.personalization) { this.getUser(); } } createButton(config: SberidSDKProps, container: HTMLElement): void { const button = new SberidButton( config, container, config.onButtonClick || this.handleButtonClick.bind(this), ); this.buttons.push(button); } handleUserChange(user?: User): void { this.user = user; if (!user) { this.notifyBanner?.hide(); } } async silentAuthorization(): Promise<FastAuthorizationResponse> { return await this.fastLogin.authorization(this.oidcParams); } async getUser(): Promise<void> { showLoader(); if (this.config.debug) { log(['Получаем информацию о пользователе'], 'info'); } const user = await this.userHelper.getUser().catch((e) => { if (this.config.debug) { log(['Ошибка при получении данных о пользователе', e], 'error'); } }); if (user) { await this.onGetUserSuccess(user); } else { if (this.config.debug) { log(['Ошибка при получении данных о пользователе'], 'error'); } } hideLoader(); } disable(): void { for (let i = 0; i < this.buttons.length; i += 1) { this.buttons[i].disable(); } } enable(): void { for (let i = 0; i < this.buttons.length; i += 1) { this.buttons[i].enable(); } } async handleButtonClick(e: Event, link?: HTMLElement): Promise<boolean> { this.w = void 0; e.preventDefault(); e.stopPropagation(); const isDisabled = link?.getAttribute('disabled') === 'disabled' || link?.getAttribute('disabled') === 'true'; if (isDisabled) { return false; } showLoader(); this.disable(); if (this.config.sa) { const params = { personalization: link?.dataset?.personalization === 'true', }; sendSberAnalytics({ eventCategory: 'Login', eventAction: 'sberid_Login_Button_Click', eventType: 'Result', extendedProperties: params, }); } const url = (<HTMLAnchorElement>e.target).closest('.sbid-button')?.getAttribute('href') || ''; if (this.user && this.config.fastLogin) { const response = await this.silentAuthorization(); if (response.success) { if (this.config.debug) { log(['Успешный быстрый вход', response.data], 'success'); } this.onSuccessCallback(response.data as SberidSDKSuccessResult); this.notifyBanner?.onClose(); } else { if (this.config.debug) { log(['Ошибка быстрого входа', response.data], 'error'); } if ( this.config.display === 'popup' && this.parser.getPlatformType(true) === 'desktop' ) { this.openDialog(url); return false; } else { window.location.href = url; } } hideLoader(); this.enable(); } else if ( this.config.display === 'popup' && this.parser.getPlatformType(true) === 'desktop' ) { this.openDialog(url); return false; } else { window.location.href = url; } return false; } async onGetUserSuccess(user?: User): Promise<void> { this.user = user; if (this.config.debug) { log(['Данные о пользователе получены', user], 'success'); } const isBannerClosed = parseInt(Cookies.get('sbid_notification_banner_closed') ?? '0', 10); if (!isBannerClosed && this.user) { await this.notifyBanner?.show(user); } if (this.user && this.config.fastLogin && this.config.fastLoginMode === 'auto') { showLoader(); this.disable(); try { const response = await this.silentAuthorization(); if (response.success) { if (this.config.debug) { log(['Успешный быстрый вход', response.data], 'success'); } if (this.onSuccessCallback) { this.onSuccessCallback(response.data as SberidSDKSuccessResult); } this.notifyBanner?.onClose(); } } catch (e: any) { if (this.config.debug) { log(['Автоматический вход не удался', e], 'error'); } } finally { hideLoader(); this.enable(); } } } static getVersion(): string { return getVersion(); } async setOIDCParams(oidc: OidcParams): Promise<void> { const params = this.universalLinkDetect.buildOidcParams(oidc); const paramsKeys = Object.keys(params); let isForceUniversalLinkDisable = (paramsKeys.includes('app_redirect_uri') && paramsKeys.includes('authApp')) || paramsKeys['is_universal_link'] === 'false'; this.oidcParams = {...params}; const isMobile = this.parser.getPlatformType(true) === 'mobile'; let baseUrl = getAuthUrl(this.config.baseUrl); if (isMobile && this.config.deeplink.enable && params.sberIDRedirect) { const sberIDRedirect = decodeURIComponent(params.sberIDRedirect); const isAllowSberIDRedirect = (this.config.deeplink.allowSberIDRedirects.length > 0 && this.config.deeplink.allowSberIDRedirects.indexOf(sberIDRedirect) !== -1) || this.config.deeplink.allowSberIDRedirects.length === 0; if (isAllowSberIDRedirect) { baseUrl = sberIDRedirect; isForceUniversalLinkDisable = true; if (this.config.deeplink.mode === 'auto') { if (this.config.debug) { log([ 'Автоматический переход по deeplink: ', buildUrl(baseUrl, this.oidcParams), ]); } window.location.href = buildUrl(baseUrl, this.oidcParams); } else if (this.config.debug) { log([ 'Будет осуществлен переход по deeplink: ', buildUrl(baseUrl, this.oidcParams), ]); } } } if (this.config.mweb2app && !isForceUniversalLinkDisable) { const universalLinkResponse = await this.universalLinkDetect.run(this.oidcParams); if (this.config.debug) { log(['Получаем данные для формирования ссылки: ', universalLinkResponse]); } this.sbUniversalLinkCallback(universalLinkResponse); } else { this.sbUniversalLinkCallback({ isUniversalLink: false, link: buildUrl(baseUrl, this.oidcParams), }); } } sbUniversalLinkCallback(params: {link: string; isUniversalLink: boolean}): void { for (let i = 0; i < this.buttons.length; i += 1) { this.buttons[i].setUrl(params.link); } } checkState(state: string): boolean { const sbidState = Cookies.get('sbid_state'); if (state && sbidState !== state) { if (this.w) { this.windowStatus = 'closed'; this.w.close(); } if (typeof this.onErrorCallback === 'function') { this.onErrorCallback({ code: 'error', error: 'invalid_state', description: ERROR_MESSAGES['invalid_state'], }); } Cookies.delete('sbid_state'); return false; } return true; } listener(event: Event & {data?: any}): void { let message = {} as SberidSDKSuccessResult & { state: string; status: string; error: string; description: string; }; try { message = JSON.parse(event.data); } catch (error) { if (this.config.debug) { log(['Ошибка при обработке postMessage', event.data], 'info'); } } if (this.config.generateState && !this.checkState(message?.state)) { return; } if (message && (message.status === 'success' || message.status === 'error')) { if (this.w) { this.windowStatus = 'closed'; this.w.close(); this.w = void 0; } if (message.status === 'success' && typeof this.onSuccessCallback === 'function') { if (this.config.sa) { sendSberAnalytics({ eventCategory: 'Login', eventAction: 'sberid_Login_Auth_Result', eventType: 'Result', result: 'success', }); } hideLoader(); this.enable(); this.onSuccessCallback(message as SberidSDKSuccessResult); } if (message.status === 'error' && typeof this.onErrorCallback === 'function') { if (this.config.sa) { const description = message.error + '_' + message.description; sendSberAnalytics({ eventCategory: 'Login', eventAction: 'sberid_Login_Auth_Result', eventType: 'Result', result: 'fail', description, }); } hideLoader(); this.enable(); this.onErrorCallback(message as SberidSDKErrorResult); } } } addWindowListener(): () => void { const listener = this.listener.bind(this); window.addEventListener('message', listener); if (this.config.debug) { log(['Добавляем обработчики при открытии окна.'], 'info'); } return function () { log(['Удаляем обработчики открытого окна.'], 'info'); return window.removeEventListener('message', listener); }; } openDialog(url: string, params?: Record<string, string>): void { if (this.config.debug) { log(['Открываем окно авторизации через Сбер ID.'], 'info'); } this.w = openDialog(url, params); if (this.w) { this.windowStatus = 'opened'; this.removeWindowListener = this.addWindowListener(); const i = setInterval(() => { if (!this.w || this.w.closed) { clearInterval(i); this.closeCallback(); } }, 100); } } closeCallback(): void { if (this.config.debug) { log(['Вызываем функцию обратного вызова при закрытии окна.'], 'info'); } if (this.windowStatus !== 'closed') { this.windowStatus = 'closed'; this.w = void 0; if (typeof this.onErrorCallback === 'function') { if (this.config.sa) { const description = 'window_closed_' + ERROR_MESSAGES['window_closed']; sendSberAnalytics({ eventCategory: 'Login', eventAction: 'sberid_Login_Auth_Result', eventType: 'Result', result: 'fail', description, }); } hideLoader(); this.enable(); this.onErrorCallback({ code: 'error', error: 'window_closed', description: ERROR_MESSAGES['window_closed'], }); } } this.removeWindowListener(); } }