@sberid/js-sdk
Version:
Javascript SDK для партнеров Сбер ID, упрощающая подключение SberbankID на сайте.
312 lines (260 loc) • 10.7 kB
text/typescript
import {BrowserName} from 'constants/common';
import {Browser} from './browser';
import {
AbstractBrowserItemModeDetect,
AbstractBrowserModeDetector,
BrowserMode,
} from './interfaces';
type WebkitNavigatorExtended = Navigator & {webkitTemporaryStorage?: any};
type SafariWindowExtended = Window &
typeof globalThis & {
safariIncognito?: any;
openDatabase?: (a: any, b: any, c: any, d: any) => void;
};
type safariBlob = {
size: number;
payload: string;
lastModifiedDate: Date;
name: string;
};
export class BrowserModeDetector implements AbstractBrowserModeDetector {
browserMode: BrowserMode = 'unknown';
retry(ready: () => boolean): Promise<boolean> {
return new Promise((resolve) => {
let iteration = 0;
const maxRetry = 50;
const interval = 10;
let isTimeout = false;
const id = window.setInterval(() => {
if (ready()) {
window.clearInterval(id);
resolve(isTimeout);
}
if (iteration++ > maxRetry) {
window.clearInterval(id);
isTimeout = true;
resolve(isTimeout);
}
}, interval);
});
}
run(): Promise<BrowserMode> {
return new Promise((resolve) => {
new BrowserFactory().browser(this).detectBrowsingMode();
this.retry(() => this.browserMode !== 'unknown').then(() => {
resolve(this.browserMode);
});
});
}
}
export class BrowserFactory {
private browserData: any = undefined;
browser(BrowserModeDetector: AbstractBrowserModeDetector): any {
if (this.browserData && typeof this.browserData === 'object') {
return this.browserData;
}
this.browserData = this.resolve(BrowserModeDetector);
return this.browserData;
}
private resolve(BrowserModeDetector: AbstractBrowserModeDetector) {
const browserName = Browser.getParser(window.navigator.userAgent).getBrowserName();
switch (browserName) {
case BrowserName.SAFARI:
return new SafariBrowser(BrowserModeDetector);
case BrowserName.FIREFOX:
return new FirefoxBrowser(BrowserModeDetector);
case BrowserName.INTERNET_EXPLORER:
return new IE10EdgeBrowser(BrowserModeDetector);
default:
if ('webkitRequestFileSystem' in window || 'requestFileSystem' in window) {
return new WebkitBrowser(BrowserModeDetector);
} else if (window.PointerEvent || 'MSPointerEvent' in window) {
return new IE10EdgeBrowser(BrowserModeDetector);
}
return new OtherBrowser(BrowserModeDetector);
}
}
}
export class WebkitBrowser implements AbstractBrowserItemModeDetect {
private navigator: WebkitNavigatorExtended = navigator;
private readonly STORAGE_QUOTA_LIMIT = 120000000;
private readonly TEMP_STORAGE_QUOTA_LIMIT = 110000000;
BrowserModeDetector: AbstractBrowserModeDetector;
constructor(BrowserModeDetector: AbstractBrowserModeDetector) {
this.BrowserModeDetector = BrowserModeDetector;
}
private callbackWhenWebkitTemporaryStorageQuotaNotLimited() {
this.BrowserModeDetector.browserMode = 'normal';
return;
}
private callbackWhenWebkitTemporaryStorageQuotaLimited() {
this.BrowserModeDetector.browserMode = 'incognito';
return;
}
private callbackWhenWebkitStorageQuotaLimited() {
this.BrowserModeDetector.browserMode = 'incognito';
return;
}
private checkWebkitTemporaryStorageQuota() {
if (
typeof this.navigator.webkitTemporaryStorage !== 'undefined' &&
typeof this.navigator.webkitTemporaryStorage.queryUsageAndQuota !== 'undefined'
) {
this.navigator.webkitTemporaryStorage.queryUsageAndQuota(
(usedBytes: number, grantedBytes: number): void => {
if (grantedBytes < this.TEMP_STORAGE_QUOTA_LIMIT) {
this.callbackWhenWebkitTemporaryStorageQuotaLimited();
} else {
this.callbackWhenWebkitTemporaryStorageQuotaNotLimited();
}
},
() => {
this.callbackWhenWebkitTemporaryStorageQuotaNotLimited();
},
);
} else {
this.callbackWhenWebkitTemporaryStorageQuotaNotLimited();
}
}
private checkStorageQuota() {
if (
typeof navigator.storage !== 'undefined' &&
typeof navigator.storage.estimate !== 'undefined'
) {
navigator.storage.estimate().then((estimate) => {
if (estimate?.quota && estimate?.quota < this.STORAGE_QUOTA_LIMIT) {
this.callbackWhenWebkitStorageQuotaLimited();
} else {
this.callbackWhenWebkitStorageQuotaNotLimited();
}
});
} else {
this.callbackWhenWebkitStorageQuotaNotLimited();
}
}
private callbackWhenWebkitStorageQuotaNotLimited() {
this.checkWebkitTemporaryStorageQuota();
return;
}
detectBrowsingMode(): void {
this.checkStorageQuota();
}
}
export class FirefoxBrowser implements AbstractBrowserItemModeDetect {
private db!: IDBOpenDBRequest;
BrowserModeDetector: AbstractBrowserModeDetector;
constructor(BrowserModeDetector: AbstractBrowserModeDetector) {
this.BrowserModeDetector = BrowserModeDetector;
}
private callbackWhenIndexedDBNotWorking(): void {
// On Firefox ESR versions, actually IndexedDB don't works.
this.BrowserModeDetector.browserMode = 'incognito';
}
private callbackWhenIndexedDBWorking(): void {
if (this.BrowserModeDetector.browserMode === 'unknown') {
this.BrowserModeDetector.retry(() => this.db.readyState === 'done').then(
(isTimeout: boolean) => {
if (isTimeout) {
return this.callbackWhenIndexedDBNotWorking();
}
if (this.db.result) {
this.BrowserModeDetector.browserMode = 'normal';
}
},
);
}
}
detectBrowsingMode(): void {
this.db = indexedDB.open('i');
this.db.onsuccess = this.callbackWhenIndexedDBWorking.bind(this);
this.db.onerror = this.callbackWhenIndexedDBNotWorking.bind(this);
}
}
export class IE10EdgeBrowser implements AbstractBrowserItemModeDetect {
BrowserModeDetector: AbstractBrowserModeDetector;
constructor(BrowserModeDetector: AbstractBrowserModeDetector) {
this.BrowserModeDetector = BrowserModeDetector;
}
detectBrowsingMode(): void {
this.BrowserModeDetector.browserMode = 'normal';
try {
if (!window.indexedDB) {
this.BrowserModeDetector.browserMode = 'incognito';
}
} catch (e) {
this.BrowserModeDetector.browserMode = 'incognito';
}
}
}
export class SafariBrowser implements AbstractBrowserItemModeDetect {
BrowserModeDetector: AbstractBrowserModeDetector;
constructor(BrowserModeDetector: AbstractBrowserModeDetector) {
this.BrowserModeDetector = BrowserModeDetector;
}
private tryFillLocalStorage(chunkSize: number): boolean {
const size = chunkSize / 4;
const content = new Array(size + 1).join('aあ');
const blob: safariBlob = {
size: chunkSize,
payload: content,
lastModifiedDate: new Date(),
name: ~~(Math.random() * 100000) + 100000 + '.txt',
};
try {
localStorage.setItem(blob.name, JSON.stringify(blob));
} catch (e) {
try {
localStorage.removeItem(blob.name);
} catch (e) {}
return false;
}
try {
localStorage.removeItem(blob.name);
} catch (e) {}
return true;
}
detectBrowsingMode(): void {
const extendWindow: SafariWindowExtended = window;
const browser = Browser.getParser(window.navigator.userAgent);
const osMajorVersion = parseInt((<string>browser.getOSVersion())?.split('.')[0], 10) || 0;
const browserMajorVersion =
parseInt((<string>browser.getBrowserVersion())?.split('.')[0], 10) || 0;
const osName = browser.getOSName(true);
if (extendWindow.safariIncognito || !navigator.cookieEnabled) {
this.BrowserModeDetector.browserMode = 'incognito';
return;
}
try {
extendWindow.openDatabase && extendWindow.openDatabase(null, null, null, null);
extendWindow.localStorage.setItem('test', '1');
} catch (e) {
this.BrowserModeDetector.browserMode = 'incognito';
}
if (this.BrowserModeDetector.browserMode === 'unknown') {
extendWindow.localStorage.removeItem('test');
if (
(osName === 'ios' && osMajorVersion >= 14) ||
(osName === 'macos' && browserMajorVersion >= 14) ||
(osName === 'ios' && browserMajorVersion === 0)
) {
this.BrowserModeDetector.browserMode = 'normal';
} else {
const LOCAL_STORAGE_DATA_SIZE = 1024 * 1024 * 5; // 5MB
if (this.tryFillLocalStorage(LOCAL_STORAGE_DATA_SIZE)) {
this.BrowserModeDetector.browserMode = 'incognito';
} else {
this.BrowserModeDetector.browserMode = 'normal';
}
}
}
}
}
export class OtherBrowser implements AbstractBrowserItemModeDetect {
BrowserModeDetector: AbstractBrowserModeDetector;
constructor(BrowserModeDetector: AbstractBrowserModeDetector) {
this.BrowserModeDetector = BrowserModeDetector;
}
detectBrowsingMode(): void {
this.BrowserModeDetector.browserMode = 'normal';
}
}