Last active
August 16, 2018 16:09
-
-
Save studentIvan/f0d07ff5215aba6c776610cfb5382561 to your computer and use it in GitHub Desktop.
Device service javascript helper
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* global window, location, navigator, Element */ | |
/* eslint comma-dangle: "off", no-prototype-builtins: "off", no-param-reassign: "off" */ | |
/** | |
* device service | |
* @version 2.0.1 | |
*/ | |
module.exports = { | |
cache: {}, | |
/** | |
* @returns {Window} | |
*/ | |
getWindowObject() { | |
if (!this.cache.hasOwnProperty('windowObject')) { | |
this.cache.windowObject = typeof window !== 'undefined' ? window : null; | |
} | |
return this.cache.windowObject; | |
}, | |
/** | |
* Check browser is real, not ssr, etc | |
* | |
* @returns {boolean} | |
*/ | |
isRealBrowser() { | |
return this.getWindowObject() !== null; | |
}, | |
/** | |
* Helper for replacement of deviceService.isRealBrowser() ? ok : no | |
* | |
* @param {*} ok | |
* @param {*} no | |
*/ | |
withRealBrowser(ok, no = null) { | |
return this.isRealBrowser() ? ok : no; | |
}, | |
/** | |
* Detect cordova app | |
* | |
* @returns {boolean} | |
*/ | |
isMobileApplication() { | |
if (!this.isRealBrowser()) { return false; } | |
return this.getWindowObject().location.protocol === 'file:'; | |
}, | |
/** Mark cordova app frame */ | |
useCordovaFrame() { | |
this.cache.cordovaFrame = true; | |
}, | |
/** | |
* Detect cordova app frame | |
* | |
* @returns {boolean} | |
*/ | |
isCordovaFrame() { | |
return this.cache.cordovaFrame; | |
}, | |
/** | |
* Simple get user-agent | |
* | |
* @returns {string} | |
*/ | |
getUserAgent() { | |
if (!this.isRealBrowser()) { return ''; } | |
if (!this.cache.hasOwnProperty('userAgent')) { | |
const wnd = this.getWindowObject(); | |
this.cache.userAgent = | |
wnd.navigator.userAgent || | |
wnd.navigator.vendor || wnd.opera; | |
} | |
return this.cache.userAgent; | |
}, | |
/** | |
* Get build environment name | |
* | |
* @returns {string} | |
*/ | |
getBuildEnvironmentName() { | |
if (!this.isRealBrowser()) { return ''; } | |
if (!this.cache.hasOwnProperty('buildEnv')) { | |
const wnd = this.getWindowObject(); | |
this.cache.buildEnv = | |
wnd.document | |
.querySelector('meta[name="build-env"]') | |
.content === '1' | |
? 'production' : 'development'; | |
} | |
return this.cache.buildEnv; | |
}, | |
/** | |
* Get current platform (OS + type) name | |
* | |
* @returns {string} | |
*/ | |
getPlatformName() { | |
if (!this.isRealBrowser()) { return ''; } | |
if (!this.cache.hasOwnProperty('platformName')) { | |
const wnd = this.getWindowObject(); | |
this.cache.platformName = 'unknown'; | |
if (this.isMobileApplication()) { | |
this.cache.platformName = this.isiOSMobileApplication() | |
? 'iOS app' : 'Android app'; | |
} | |
else if (this.isAppleMobileBrowser()) { | |
this.cache.platformName = this.isiOSHomeScreenApp() | |
? 'iOS homescreen' : 'iOS web'; | |
} | |
else if (this.isCommonChromeAndroid()) { | |
this.cache.platformName = typeof wnd.matchMedia !== 'undefined' | |
&& wnd.matchMedia('(display-mode: standalone)').matches | |
? 'Android standalone' : 'Android web'; | |
} | |
else { | |
this.cache.platformName = this.getWebOSVersion(); | |
} | |
} | |
return this.cache.platformName; | |
}, | |
/** | |
* Get android version | |
* | |
* @returns {string} | |
*/ | |
getAndroidVersion() { | |
if (!this.isRealBrowser()) { return false; } | |
if (!this.cache.hasOwnProperty('androidVersion')) { | |
const ua = this.getUserAgent(); | |
const result = ua.match(/Android\s([0-9.]*)/i); | |
this.cache.androidVersion = Object.prototype.toString.call(result) === '[object Array]' | |
? result[result.length - 1] : result; | |
} | |
return this.cache.androidVersion; | |
}, | |
/** | |
* Get user operation system name | |
* | |
* @returns {string} | |
*/ | |
getWebOSVersion() { | |
if (!this.isRealBrowser()) { return ''; } | |
if (!this.cache.hasOwnProperty('webOSVersion')) { | |
const wnd = this.getWindowObject(); | |
const appVer = wnd.navigator.appVersion; | |
let osName = 'unknown'; | |
if (appVer.indexOf('Win') !== -1) { | |
osName = 'Windows'; | |
} | |
else if (appVer.indexOf('Mac') !== -1) { | |
osName = 'MacOS'; | |
} | |
else if (appVer.indexOf('X11') !== -1) { | |
osName = 'Unix'; | |
} | |
else if (appVer.indexOf('Linux') !== -1) { | |
osName = 'Linux'; | |
} | |
this.cache.webOSVersion = osName; | |
} | |
return this.cache.webOSVersion; | |
}, | |
/** | |
* Check that we work with landscape orietation | |
* | |
* @returns {boolean} | |
*/ | |
isLandscape() { | |
if (!this.isRealBrowser()) { return false; } | |
const wnd = this.getWindowObject(); | |
return wnd.innerHeight < wnd.innerWidth; | |
}, | |
/** | |
* Check you have deal with Facebook in-app browser | |
* | |
* @returns {boolean} | |
*/ | |
isFacebookApp() { | |
if (!this.isRealBrowser()) { return false; } | |
if (!this.cache.hasOwnProperty('isFacebookApp')) { | |
const ua = this.getUserAgent(); | |
this.cache.isFacebookApp = | |
Boolean((ua.indexOf('FBAN') > -1) || (ua.indexOf('FBAV') > -1)); | |
} | |
return this.cache.isFacebookApp; | |
}, | |
/** | |
* Check you have deal with iframe specific url | |
* | |
* @returns {boolean} | |
*/ | |
isFramed() { | |
if (!this.isRealBrowser()) { return false; } | |
if (!this.cache.hasOwnProperty('isFramed')) { | |
this.cache.isFramed = this.getWindowObject().location | |
.pathname.indexOf('-frame') !== -1; | |
} | |
return this.cache.isFramed; | |
}, | |
/** | |
* Check you have deal with Instagram in-app browser | |
* | |
* @returns {boolean} | |
*/ | |
isInstagramApp() { | |
if (!this.isRealBrowser()) { return false; } | |
if (!this.cache.hasOwnProperty('isInstagramApp')) { | |
const ua = this.getUserAgent(); | |
this.cache.isInstagramApp = | |
Boolean(ua.indexOf('Instagram') > -1); | |
} | |
return this.cache.isInstagramApp; | |
}, | |
/** | |
* Check you have deal with iOS Facebook in-app browser | |
* | |
* @returns {boolean} | |
*/ | |
isiOSFacebookApp() { | |
if (!this.isRealBrowser()) { return false; } | |
if (!this.cache.hasOwnProperty('isiOSFacebookApp')) { | |
this.cache.isiOSFacebookApp = | |
this.isFacebookApp() && this.getUserAgent().indexOf('Android') === -1; | |
} | |
return this.cache.isiOSFacebookApp; | |
}, | |
/** | |
* Check you have deal with iOS Instagram in-app browser | |
* | |
* @returns {boolean} | |
*/ | |
isiOSInstagramApp() { | |
if (!this.isRealBrowser()) { return false; } | |
if (!this.cache.hasOwnProperty('isiOSInstagramApp')) { | |
this.cache.isiOSInstagramApp = | |
this.isInstagramApp() && this.getUserAgent().indexOf('Android') === -1; | |
} | |
return this.cache.isiOSInstagramApp; | |
}, | |
/** | |
* Check you have deal with homescreen ios app | |
* | |
* @returns {boolean} | |
*/ | |
isiOSHomeScreenApp() { | |
if (!this.isRealBrowser()) { return false; } | |
const wnd = this.getWindowObject(); | |
return this.isAppleMobileBrowser() | |
&& !(typeof wnd.navigator.standalone !== 'undefined' | |
&& !wnd.navigator.standalone); | |
}, | |
/** | |
* Check you have deal with cordova ios app | |
* | |
* @returns {boolean} | |
*/ | |
isiOSMobileApplication() { | |
if (!this.isRealBrowser()) { return false; } | |
const wnd = this.getWindowObject(); | |
return this.isAppleMobileBrowser() | |
&& this.isMobileApplication(); | |
}, | |
/** | |
* Check you have deal with iPhone/iPad browser like Safari or Chrome | |
* | |
* @returns {boolean} | |
*/ | |
isAppleMobileBrowser() { | |
if (!this.isRealBrowser()) { return false; } | |
if (!this.cache.hasOwnProperty('isAppleMobileBrowser')) { | |
const wnd = this.getWindowObject(); | |
const isChrome = !!wnd.chrome && !!wnd.chrome.webstore; | |
this.cache.isAppleMobileBrowser = | |
Boolean(/iPhone|iPad|iPod/i.test(this.getUserAgent()) && !isChrome); | |
} | |
return this.cache.isAppleMobileBrowser; | |
}, | |
/** | |
* Check you have deal with Firefox | |
* | |
* @returns {boolean} | |
*/ | |
isFirefox() { | |
if (!this.isRealBrowser()) { return false; } | |
if (!this.cache.hasOwnProperty('isFirefox')) { | |
this.cache.isFirefox = | |
Boolean(/Firefox|FxiOS/i.test(this.getUserAgent())); | |
} | |
return this.cache.isFirefox; | |
}, | |
/** | |
* Check navigator online state | |
* | |
* @returns {boolean} | |
*/ | |
checkOnlineState() { | |
if (!this.isRealBrowser()) { return false; } | |
const wnd = this.getWindowObject(); | |
return wnd.navigator.onLine; | |
}, | |
/** | |
* Check you have deal with iPhone/iPad browser like Safari or Chrome | |
* Also you can use modern methods like scrollBy | |
* | |
* @returns {boolean} | |
*/ | |
isModernAppleMobileBrowser() { | |
if (!this.isRealBrowser()) { return false; } | |
if (!this.cache.hasOwnProperty('isModernAppleMobileBrowser')) { | |
const wnd = this.getWindowObject(); | |
this.cache.isModernAppleMobileBrowser = | |
Boolean(this.isAppleMobileBrowser() | |
&& typeof wnd.scrollBy === 'function' | |
&& typeof wnd.requestAnimationFrame === 'function'); | |
} | |
return this.cache.isModernAppleMobileBrowser; | |
}, | |
/** | |
* Check you have deal with iPhone X | |
* | |
* @returns {boolean} | |
*/ | |
isiPhoneXPortrait() { | |
if (!this.isRealBrowser()) { return false; } | |
const wnd = this.getWindowObject(); | |
return Boolean(this.isAppleMobileBrowser() | |
&& wnd.devicePixelRatio === 3 && wnd.innerWidth === 375 | |
&& (wnd.innerHeight === 635 || ( | |
this.isiOSInstagramApp() && (wnd.innerHeight === 690)) | |
|| (this.isiOSFacebookApp() && (wnd.innerHeight === 609)) | |
|| (this.isiOSHomeScreenApp() && (wnd.innerHeight === 768)) | |
|| (this.isMobileApplication() && (wnd.innerHeight === 754)) | |
|| (this.isMobileApplication() && (wnd.innerHeight === 812)) | |
|| (this.isMobileApplication() && (wnd.screen.height === 812)) | |
|| (!this.isMobileApplication() && this.isFramed() && (wnd.screen.height === 812)) | |
)); | |
}, | |
/** | |
* Check that it is iPhoneSE/iPhone5S screen | |
* | |
* @returns {boolean} | |
*/ | |
isiPhone5orSE() { | |
if (!this.isRealBrowser()) { return false; } | |
const wnd = this.getWindowObject(); | |
return Boolean(this.isAppleMobileBrowser() | |
&& wnd.screen.width === 320 | |
&& wnd.screen.height === 568); | |
}, | |
/** | |
* Check that it is iPhone4/iPhone4S screen | |
* | |
* @returns {boolean} | |
*/ | |
isiPhone4Sor4() { | |
if (!this.isRealBrowser()) { return false; } | |
const wnd = this.getWindowObject(); | |
return Boolean(this.isAppleMobileBrowser() | |
&& wnd.screen.width === 320 | |
&& wnd.screen.height === 480); | |
}, | |
/** | |
* Get device GPU name | |
* | |
* @returns {string} | |
*/ | |
getGPUName() { | |
if (!this.isRealBrowser()) { return false; } | |
if (!this.cache.hasOwnProperty('gpuName')) { | |
const wnd = this.getWindowObject(); | |
this.cache.gpuName = 'unknown'; | |
const canvas = wnd.document.createElement('canvas'); | |
if (canvas && typeof canvas.getContext === 'function') { | |
const context = canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); | |
if (context && typeof context.getExtension === 'function') { | |
const info = context.getExtension('WEBGL_debug_renderer_info'); | |
if (info) { | |
this.cache.gpuName = context | |
.getParameter(info.UNMASKED_RENDERER_WEBGL) || 'unknown'; | |
} | |
} | |
} | |
} | |
return this.cache.gpuName; | |
}, | |
/** | |
* Check that it is Galaxy S8+/Note 8/S8 screen | |
* | |
* @returns {boolean} | |
*/ | |
isGalaxyS8Series() { | |
if (!this.isRealBrowser()) { return false; } | |
return Boolean(/SM-G950F|SM-N950F|SM-G955F/i.test(this.getUserAgent())); | |
}, | |
/** | |
* Check that it is Galaxy S8+/Note 8/S8 screen | |
* | |
* @returns {boolean} | |
*/ | |
isGalaxyS9Series() { | |
if (!this.isRealBrowser()) { return false; } | |
return Boolean(/SM-G965F|SM-G960F/i.test(this.getUserAgent())); | |
}, | |
/** | |
* Check that it is Galaxy S8+/Note 8/S8 screen | |
* | |
* @returns {boolean} | |
*/ | |
isNewPixelSeries() { | |
if (!this.isRealBrowser()) { return false; } | |
return Boolean(/Pixel 2/i.test(this.getUserAgent())); | |
}, | |
/** | |
* Check that it is simple android chrome | |
* | |
* @returns {boolean} | |
*/ | |
isCommonChromeAndroid() { | |
if (!this.isRealBrowser()) { return false; } | |
if (!this.cache.hasOwnProperty('isCommonChromeAndroid')) { | |
this.cache.isCommonChromeAndroid = | |
Boolean(this.isGoogleChrome() && /Android/i.test(this.getUserAgent())); | |
} | |
return this.cache.isCommonChromeAndroid; | |
}, | |
/** | |
* Check that it is android cordova device from apk | |
* | |
* @returns {boolean} | |
*/ | |
isCordovaAndroid() { | |
/* global device */ | |
if (typeof device === 'undefined') { return false; } | |
return device && device.platform === 'Android'; | |
}, | |
/** | |
* Check that it is android chrome with low height | |
* | |
* @param {number=550} normalHeight | |
* @param {number=102} verticalPadding | |
* | |
* @returns {boolean} | |
*/ | |
isLowHeightAndroid(normalHeight = 550, verticalPadding = 102) { | |
if (!this.isRealBrowser()) { return false; } | |
if (!this.cache.hasOwnProperty('isLowHeightAndroid')) { | |
this.cache.isLowHeightAndroid = | |
Boolean(this.isCommonChromeAndroid() | |
&& this.getWindowObject().innerHeight < normalHeight + verticalPadding); | |
} | |
return this.cache.isLowHeightAndroid; | |
}, | |
/** | |
* Check that it is Google Chrome | |
* | |
* @returns {boolean} | |
*/ | |
isGoogleChrome() { | |
if (!this.isRealBrowser()) { return false; } | |
if (!this.cache.hasOwnProperty('isGoogleChrome')) { | |
const wnd = this.getWindowObject(); | |
const isChromium = wnd.chrome; | |
const vendorName = wnd.navigator.vendor; | |
const isOpera = wnd.navigator.userAgent.indexOf('OPR') > -1; | |
const isIEedge = wnd.navigator.userAgent.indexOf('Edge') > -1; | |
const isIOSChrome = wnd.navigator.userAgent.match('CriOS'); | |
this.cache.isGoogleChrome = isIOSChrome || ( | |
isChromium !== null && | |
typeof isChromium !== 'undefined' && | |
vendorName === 'Google Inc.' && | |
isOpera === false && | |
isIEedge === false | |
); | |
} | |
return this.cache.isGoogleChrome; | |
}, | |
/** | |
* @param {number=800} doubleColumnsOptimalWidth | |
* @param {number=550} imageOptimalSize | |
* | |
* @returns {number} | |
*/ | |
getAutoColumnsCount(doubleColumnsOptimalWidth = 800, imageOptimalSize = 550) { | |
if (!this.isRealBrowser()) { return 2; } | |
const wnd = this.getWindowObject(); | |
return wnd.innerWidth < doubleColumnsOptimalWidth | |
? 2 : Math.floor(wnd.innerWidth / imageOptimalSize); | |
}, | |
/** | |
* Get images widht/height for scrolling cell | |
* | |
* @param {number} colSize can be configured automatically | |
* @param {number=1.81812} aspectRatio | |
* @param {number=20} paddings | |
* | |
* @returns {{ width:number, height:number }} | |
*/ | |
calculateRowImage(colSize = this.getAutoColumnsCount(), aspectRatio = 1.81812, paddings = 20) { | |
if (!this.isRealBrowser()) { return { width: 170, height: 170 * aspectRatio }; } | |
const width = (this.getWindowObject().innerWidth - ((paddings * colSize) + paddings)) / colSize; | |
const height = width * aspectRatio; | |
return { width, height }; | |
}, | |
/** | |
* Get current screen image better cover or contain | |
* | |
* @param {number=1.81812} aspectRatio | |
* | |
* @returns {boolean} | |
*/ | |
coverBetterThanContain(aspectRatio = 1.81812) { | |
if (!this.isRealBrowser()) { return false; } | |
const wnd = this.getWindowObject(); | |
return wnd.innerHeight / wnd.innerWidth > (aspectRatio - 0.05); | |
}, | |
/** | |
* "freeze" the page and prevent scrolling | |
* @param {string[]} [ids] - common ids list for current page to prevent scroll | |
* @param {Element} [componentRef] - react component reference, optional | |
*/ | |
fixPageWithIds(ids = [], componentRef = null) { | |
const wnd = this.getWindowObject(); | |
[wnd.document.body, wnd.document.getElementById('app')].forEach((block) => { | |
block.style.overflow = 'hidden'; | |
block.style.position = 'fixed'; | |
block.style.width = `${ wnd.innerWidth }px`; | |
}); | |
ids.forEach((id) => { | |
const element = componentRef | |
? componentRef.querySelector(`#${ id }`) | |
: wnd.document.getElementById(id); | |
if (element) { | |
element.style.overflow = 'hidden'; | |
} | |
}); | |
}, | |
/** | |
* "unfreeze" the page and scrolling | |
* @param {string[]} ids - common ids list for current page for scroll | |
* @param {Element} [componentRef] - react component reference, optional | |
*/ | |
resolvePageWithIds(ids = [], componentRef = null) { | |
const wnd = this.getWindowObject(); | |
const blocks = [wnd.document.body, wnd.document.getElementById('app')]; | |
ids.forEach(id => blocks.push(componentRef | |
? componentRef.querySelector(`#${ id }`) | |
: wnd.document.getElementById(id))); | |
blocks.forEach(block => !block || block.removeAttribute('style')); | |
}, | |
/** | |
* send event to element | |
* @param {Element} element | |
* @param {string} eventType | |
*/ | |
fireEvent(element, eventType) { | |
if (element.fireEvent) { | |
element.fireEvent(`on${ eventType }`); | |
} | |
else { | |
const evtObject = document.createEvent('Events'); | |
evtObject.initEvent(eventType, true, false); | |
element.dispatchEvent(evtObject); | |
} | |
}, | |
/** | |
* Save data on disc (localstorage helper) | |
* @param {string} key | |
* @param {string} value | |
*/ | |
saveDataOnDisc(key, value) { | |
if (this.isRealBrowser() && this.getWindowObject().localStorage) { | |
this.getWindowObject().localStorage.setItem(key, value); | |
} | |
}, | |
/** | |
* Read data from disc (localstorage helper) | |
* @param {string} key | |
* @returns {string|null} | |
*/ | |
readDataFromDisc(key) { | |
if (this.isRealBrowser() && this.getWindowObject().localStorage) { | |
return this.getWindowObject().localStorage.getItem(key); | |
} | |
return null; | |
}, | |
/** | |
* method to call app url with iframe instead of location, | |
* for some reason this is needed in the checkout. | |
* | |
* @param {string} url | |
*/ | |
callUrlWithIframe(url) { | |
let iframe = this.getWindowObject() | |
.document.createElement('IFRAME'); | |
iframe.setAttribute('src', url); | |
this.getWindowObject().document | |
.documentElement.appendChild(iframe); | |
iframe.parentNode.removeChild(iframe); | |
iframe = null; | |
}, | |
/** | |
* inject js-script file dynamically | |
* @param {string} src | |
* @param {boolean} [async] | |
* @returns {Promise<*>} | |
*/ | |
injectScript(src, async = true) { | |
if (!this.isRealBrowser()) { return false; } | |
if (this.cache.hasOwnProperty(`injected:${ src }`)) { | |
return Promise.resolve(); | |
} | |
const wnd = this.getWindowObject(); | |
return new Promise((resolve, reject) => { | |
const scriptElement = wnd.document.createElement('script'); | |
scriptElement.src = src; | |
scriptElement.async = async; | |
let errorHandler = () => {}; | |
let loadHandler = () => {}; | |
errorHandler = () => { | |
scriptElement.removeEventListener('load', loadHandler); | |
scriptElement.removeEventListener('error', errorHandler); | |
delete this.cache[`injected:${ src }`]; | |
reject(new Error(`Error loading script: ${ src }`)); | |
}; | |
loadHandler = () => { | |
scriptElement.removeEventListener('load', loadHandler); | |
scriptElement.removeEventListener('error', errorHandler); | |
this.cache[`injected:${ src }`] = true; | |
resolve(); | |
}; | |
scriptElement.addEventListener('load', loadHandler); | |
scriptElement.addEventListener('error', errorHandler); | |
wnd.document.body.appendChild(scriptElement); | |
}); | |
}, | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment