Skip to content

Instantly share code, notes, and snippets.

@studentIvan
Last active August 16, 2018 16:09
Show Gist options
  • Save studentIvan/f0d07ff5215aba6c776610cfb5382561 to your computer and use it in GitHub Desktop.
Save studentIvan/f0d07ff5215aba6c776610cfb5382561 to your computer and use it in GitHub Desktop.
Device service javascript helper
/* 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