diff options
author | Claudio Guglielmo | 2019-06-25 16:08:34 +0000 |
---|---|---|
committer | Claudio Guglielmo | 2019-07-04 16:34:43 +0000 |
commit | aaec8fc45d6d66bf07e095cb39672a23ef5d738b (patch) | |
tree | fa136287fe22468651ea606bf7b94ac488929927 | |
parent | f7b3c4b762a53456f7cf69c17f4ce4131534b733 (diff) | |
download | org.eclipse.scout.rt-aaec8fc45d6d66bf07e095cb39672a23ef5d738b.tar.gz org.eclipse.scout.rt-aaec8fc45d6d66bf07e095cb39672a23ef5d738b.tar.xz org.eclipse.scout.rt-aaec8fc45d6d66bf07e095cb39672a23ef5d738b.zip |
Add device to test singleton pattern
-rw-r--r-- | eclipse-scout/src/scout/App.js | 34 | ||||
-rw-r--r-- | eclipse-scout/src/scout/ErrorHandler.js | 6 | ||||
-rw-r--r-- | eclipse-scout/src/scout/index.js | 1 | ||||
-rw-r--r-- | eclipse-scout/src/scout/session/Session.js | 5 | ||||
-rw-r--r-- | eclipse-scout/src/scout/util/Device.js | 613 |
5 files changed, 641 insertions, 18 deletions
diff --git a/eclipse-scout/src/scout/App.js b/eclipse-scout/src/scout/App.js index bd14769e64..9d267c2b87 100644 --- a/eclipse-scout/src/scout/App.js +++ b/eclipse-scout/src/scout/App.js @@ -11,26 +11,24 @@ import * as models from './util/models'; import JQueryUtils from './util/JQueryUtils'; import { JsonResponseError } from './constants'; -let scout_appListeners = []; -let scout_errorHandler = null; - -// FIXME [awe] ES6: talk about this pattern, see: https://k94n.com/es6-modules-single-instance-pattern -// Let used in global scope makes sure 'instance' is not reused. -export let instance = null; +let instance = null; export default class App { + static _listeners = []; constructor() { this.events = this._createEventSupport(); this.sessions = []; - /// register the listeners which were added to scout before the app is created - scout_appListeners.forEach(function(listener) { + + // register the listeners which were added to scout before the app is created + App._listeners .forEach(function(listener) { this.addListener(listener); }, this); - scout_appListeners = []; + App._listeners = []; instance = this; - scout_errorHandler = this._createErrorHandler(); + + this.errorHandler = this._createErrorHandler(); } getSessions() { // FIXME [awe] ES6: discuss where to put the sessions @@ -240,7 +238,7 @@ export default class App { * the stack trace is much longer :) */ _installErrorHandler() { - window.onerror = scout_errorHandler.windowErrorHandler; + window.onerror = this.errorHandler.windowErrorHandler; }; _createErrorHandler() { @@ -340,7 +338,7 @@ export default class App { $error.appendDiv('startup-error-title', 'The application could not be started'); var args = scout.argumentsToArray(arguments).slice(1); - var errorInfo = scout_errorHandler.handle(args); + var errorInfo = this.errorHandler.handle(args); if (errorInfo.message) { $error.appendDiv('startup-error-message', errorInfo.message); } @@ -391,5 +389,17 @@ export default class App { this.events.removeListener(listener); }; + static addListener(type, func) { + var listener = { + type: type, + func: func + }; + if (instance) { + instance.events.addListener(listener); + } else { + App._listeners.push(listener); + } + return listener; + }; } diff --git a/eclipse-scout/src/scout/ErrorHandler.js b/eclipse-scout/src/scout/ErrorHandler.js index 56ad498d56..ffd0976437 100644 --- a/eclipse-scout/src/scout/ErrorHandler.js +++ b/eclipse-scout/src/scout/ErrorHandler.js @@ -1,6 +1,6 @@ import * as $ from 'jquery'; import * as strings from './util/strings'; -import { instance as app } from './App'; +import App from './App'; export default class ErrorHandler { @@ -151,8 +151,8 @@ export default class ErrorHandler { // Note: The error handler is installed globally and we cannot tell in which scout session the error happened. // We simply use the first scout session to display the message box and log the error. This is not ideal in the // multi-session-case (portlet), but currently there is no other way. Besides, this feature is not in use yet. - if (app.getSessions().length > 0) { - var session = app.getSessions()[0]; + if (App.get().getSessions().length > 0) { + var session = App.get().getSessions()[0]; if (this.displayError) { this._showMessageBox(session, errorInfo.message, errorInfo.code, errorInfo.log); } diff --git a/eclipse-scout/src/scout/index.js b/eclipse-scout/src/scout/index.js index edf15304a1..0659a1b2fb 100644 --- a/eclipse-scout/src/scout/index.js +++ b/eclipse-scout/src/scout/index.js @@ -21,3 +21,4 @@ export { default as Desktop } from './desktop/Desktop'; export { default as NullWidget } from './widget/NullWidget'; export { default as ViewButton } from './desktop/ViewButton'; export { default as OutlineViewButton } from './outline/OutlineViewButton'; +export { default as Device } from './util/Device'; diff --git a/eclipse-scout/src/scout/session/Session.js b/eclipse-scout/src/scout/session/Session.js index 4ce0a2f947..7a71c93431 100644 --- a/eclipse-scout/src/scout/session/Session.js +++ b/eclipse-scout/src/scout/session/Session.js @@ -10,7 +10,6 @@ import DetachHelper from '../util/DetachHelper'; import { Severity, FileInput, BackgroundJobPollingStatus, JsonResponseError } from '../constants'; import RemoteEvent from './RemoteEvent'; import URL from '../util/URL'; -import { instance as app } from '../App'; export default class Session { @@ -282,8 +281,8 @@ export default class Session { if (this.clientSessionId) { request.clientSessionId = this.clientSessionId; } - if (app.version) { - request.version = app.version; + if (App.get().version) { + request.version = App.get().version; } request.userAgent = this.userAgent; request.sessionStartupParams = this._createSessionStartupParams(); diff --git a/eclipse-scout/src/scout/util/Device.js b/eclipse-scout/src/scout/util/Device.js new file mode 100644 index 0000000000..f90bab1624 --- /dev/null +++ b/eclipse-scout/src/scout/util/Device.js @@ -0,0 +1,613 @@ +/******************************************************************************* + * Copyright (c) 2014-2018 BSI Business Systems Integration AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * BSI Business Systems Integration AG - initial API and implementation + ******************************************************************************/ +/* global FastClick */ + +import * as scout from '../scout'; +import App from '../App'; + +let instance = null; + +/** + * Provides information about the device and its supported features.<p> + * The informations are detected lazily. + * + * @singleton + */ +export default class Device { + constructor(model) { + // user agent string from browser + this.userAgent = model.userAgent; + this.features = {}; + this.system = System.UNKNOWN; + this.type = Type.DESKTOP; + this.browser = Browser.UNKNOWN; + this.browserVersion = 0; + + // --- device specific configuration + // initialize with empty string so that it can be used without calling initUnselectableAttribute() + // this property is used with regular JQuery attr(key, value) Syntax and in cases where we create + // DOM elements by creating a string. + this.unselectableAttribute = DEFAULT_UNSELECTABLE_ATTRIBUTE; + this.tableAdditionalDivRequired = false; + + if (this.userAgent) { + this._parseSystem(); + this._parseSystemVersion(); + this._parseBrowser(); + this._parseBrowserVersion(); + } + } + + /** + * Called during bootstrap by index.html before the session startup.<p> + * Precalculates the value of some attributes to store them + * in a static way (and prevent many repeating function calls within loops).<p> + * Also loads device specific scripts (e.g. fast click for ios devices) + */ + bootstrap() { + var promises = []; + + // Precalculate value and store in a simple property, to prevent many function calls inside loops (e.g. when generating table rows) + this.unselectableAttribute = this.getUnselectableAttribute(); + this.tableAdditionalDivRequired = this.isTableAdditionalDivRequired(); + this.scrollbarWidth = this._detectScrollbarWidth(); + this.type = this._detectType(this.userAgent); + + if (this._needsFastClick()) { + // We use Fastclick to prevent the 300ms delay when touching an element. + promises.push(this._loadFastClickDeferred()); + } else if (this.isIos()) { + this._installActiveHandler(); + } + + return promises; + } + + /** + * The 300ms delay exists because the browser does not know whether the user wants to just tab or wants to zoom using double tab. + * Therefore most browsers add the delay only if zoom is enabled. This works for firefox, chrome (>=32) and safari/ios (>=9.3). + * It does not work if safari is opened in standalone/homescreen mode or in cordova. For IE (and safari since ios 9.3) it can be disabled using a css property called touch-action. + * By default, zooming is disabled and home screen mode is enabled, see meta tags viewport and apple-mobile-web-app-capable in head.html + * <p> + * @return true if it is an older iOS (< 9.3), running in homescreen mode or running in a cordova container. Otherwise false. + */ + _needsFastClick() { + if (!this.isIos()) { + // Currently only IOS still has the issue -> don't load the script for other systems and browsers + return false; + } + + if (this.systemVersion >= 9.3 && !this.isStandalone() && this.browser !== Browser.UNKNOWN) { + // With iOS >= 9.3 the delay is gone if zooming is disabled, but not for the home screen / web app mode. + // It is also necessary if running in a cordova container (browser is set to unknown in that case) + return false; + } + + // -> load only for older IOS devices or if running in home screen mode or cordova + return true; + } + + _loadFastClickDeferred() { + return this._loadScriptDeferred('res/fastclick-1.0.6.js', function() { + FastClick.attach(document.body); + $.log.isInfoEnabled() && $.log.info('FastClick script loaded and attached'); + }); + } + + _loadScriptDeferred(scriptUrl, doneFunc) { + return $ + .injectScript(scriptUrl) + .done(doneFunc); + } + + /** + * IOs does only trigger :active when touching an element if a touchstart listener is attached + * Unfortunately, the :active is also triggered when scrolling, there is no delay. + * To fix this we would have to work with a custom active class which will be toggled on touchstart/end + */ + _installActiveHandler() { + document.addEventListener('touchstart', function() { + }, false); + } + + hasOnScreenKeyboard() { + return this.supportsFeature('_onScreenKeyboard', function() { + return this.isIos() || this.isAndroid() || this.isWindowsTabletMode(); + }.bind(this)); + } + + /** + * Returns if the current browser includes the padding-right-space in the scrollWidth calculations.<br> + * Such a browser increases the scrollWidth only if the text-content exceeds the space <i>including</i> the right-padding. + * This means the scrollWidth is equal to the clientWidth until the right-padding-space is consumed as well. + */ + isScrollWidthIncludingPadding() { + return this.isInternetExplorer() || this.isFirefox() || this.isEdge(); + } + + /** + * Safari shows a tooltip if ellipsis are displayed due to text truncation. This is fine but, unfortunately, it cannot be prevented. + * Because showing two tooltips at the same time (native and custom) is bad, the custom tooltip cannot be displayed. + * @returns {Boolean} + */ + isCustomEllipsisTooltipPossible() { + return this.browser !== Browser.SAFARI; + } + + isIos() { + return System.IOS === this.system; + } + + isEdge() { + return Browser.EDGE === this.browser; + } + + isInternetExplorer() { + return Browser.INTERNET_EXPLORER === this.browser; + } + + isFirefox() { + return Browser.FIREFOX === this.browser; + } + + /** + * Compared to isIos() this function uses navigator.platform instead of navigator.userAgent to check whether the app runs on iOS. + * Most of the time isIos() is the way to go. + * This function was mainly introduced to detect whether it is a real iOS or an emulated one (e.g. using chrome emulator). + * @returns true if the platform is iOS, false if not (e.g. if chrome emulator is running) + */ + isIosPlatform() { + return /iPad|iPhone|iPod/.test(navigator.platform); + } + + isAndroid() { + return System.ANDROID === this.system; + } + + /** + * The best way we have to detect a Microsoft Surface Tablet in table mode is to check if + * the scrollbar width is 0 pixel. In desktop mode the scrollbar width is > 0 pixel. + */ + isWindowsTabletMode() { + return System.WINDOWS === this.system && this.systemVersion >= 10 && this.scrollbarWidth === 0; + } + + /** + * @returns true if navigator.standalone is true which is the case for iOS home screen mode + */ + isStandalone() { + return !!window.navigator.standalone; + } + + // TODO [awe] Scout 10.0 - remove functions required for IE 9 support, also check FocusManager#_handleIEEvent + + /** + * This method returns false for very old browsers. Basically we check for the first version + * that supports ECMAScript 5. This methods excludes all browsers that are known to be + * unsupported, all others (e.g. unknown engines) are allowed by default. + */ + isSupportedBrowser(browser, version) { + browser = scout.nvl(browser, this.browser); + version = scout.nvl(version, this.browserVersion); + var browsers = Browser; + if ((browser === browsers.INTERNET_EXPLORER && version < 11) || + (browser === browsers.CHROME && version < 40) || + (browser === browsers.FIREFOX && version < 35) || + (browser === browsers.SAFARI && version < 8)) { + return false; + } + return true; + } + + /** + * Can not detect type until DOM is ready because we must create a DIV to measure the scrollbars. + */ + _detectType(userAgent) { + if (System.ANDROID === this.system) { + if (userAgent.indexOf('Mobile') > -1) { + return Type.MOBILE; + } else { + return Type.TABLET; + } + } else if (System.IOS === this.system) { + if (userAgent.indexOf('iPad') > -1) { + return Type.TABLET; + } else { + return Type.MOBILE; + } + } else if (this.isWindowsTabletMode()) { + return Type.TABLET; + } + return Type.DESKTOP; + } + + _parseSystem() { + var userAgent = this.userAgent; + if (userAgent.indexOf('iPhone') > -1 || userAgent.indexOf('iPad') > -1) { + this.system = System.IOS; + } else if (userAgent.indexOf('Android') > -1) { + this.system = System.ANDROID; + } else if (userAgent.indexOf('Windows') > -1) { + this.system = System.WINDOWS; + } + } + + /** + * Currently only supports IOS + */ + _parseSystemVersion() { + var versionRegex, + userAgent = this.userAgent; + + if (this.system === System.IOS) { + versionRegex = / OS ([0-9]+\.?[0-9]*)/; + // replace all _ with . + userAgent = userAgent.replace(/_/g, '.'); + } else if (this.system === System.WINDOWS) { + versionRegex = /Windows NT ([0-9]+\.?[0-9]*)/; + } + + if (versionRegex) { + this.systemVersion = this._parseVersion(userAgent, versionRegex); + } + } + + _parseBrowser() { + var userAgent = this.userAgent; + + if (userAgent.indexOf('Firefox') > -1) { + this.browser = Browser.FIREFOX; + } else if (userAgent.indexOf('MSIE') > -1 || userAgent.indexOf('Trident') > -1) { + this.browser = Browser.INTERNET_EXPLORER; + } else if (userAgent.indexOf('Edge') > -1) { + // must check for Edge before we do other checks, because the Edge user-agent string + // also contains matches for Chrome and Webkit. + this.browser = Browser.EDGE; + } else if (userAgent.indexOf('Chrome') > -1) { + this.browser = Browser.CHROME; + } else if (userAgent.indexOf('Safari') > -1) { + this.browser = Browser.SAFARI; + } + } + + /** + * Version regex only matches the first number pair + * but not the revision-version. Example: + * - 21 match: 21 + * - 21.1 match: 21.1 + * - 21.1.3 match: 21.1 + */ + _parseBrowserVersion() { + var versionRegex, + browsers = Browser, + userAgent = this.userAgent; + + if (this.browser === browsers.INTERNET_EXPLORER) { + // with internet explorer 11 user agent string does not contain the 'MSIE' string anymore + // additionally in new version the version-number after Trident/ is not the browser-version + // but the engine-version. + if (userAgent.indexOf('MSIE') > -1) { + versionRegex = /MSIE ([0-9]+\.?[0-9]*)/; + } else { + versionRegex = /rv:([0-9]+\.?[0-9]*)/; + } + } else if (this.browser === browsers.EDGE) { + versionRegex = /Edge\/([0-9]+\.?[0-9]*)/; + } else if (this.browser === browsers.SAFARI) { + versionRegex = /Version\/([0-9]+\.?[0-9]*)/; + } else if (this.browser === browsers.FIREFOX) { + versionRegex = /Firefox\/([0-9]+\.?[0-9]*)/; + } else if (this.browser === browsers.CHROME) { + versionRegex = /Chrome\/([0-9]+\.?[0-9]*)/; + } + if (versionRegex) { + this.browserVersion = this._parseVersion(userAgent, versionRegex); + } + } + + _parseVersion(userAgent, versionRegex) { + var matches = versionRegex.exec(userAgent); + if (Array.isArray(matches) && matches.length === 2) { + return parseFloat(matches[1]); + } + } + + supportsFeature(property, checkFunc) { + if (this.features[property] === undefined) { + this.features[property] = checkFunc(property); + } + return this.features[property]; + } + + /** + * Currently this method should be used when you want to check if the device is "touch only" - + * which means the user has no keyboard or mouse. Some hybrids like Surface tablets in desktop mode are + * still touch devices, but support keyboard and mouse at the same time. In such cases this method will + * return false, since the device is not touch only. + * + * Currently this method returns the same as hasOnScreenKeyboard(). Maybe the implementation here will be + * different in the future. + */ + supportsTouch() { + return this.supportsFeature('_touch', this.hasOnScreenKeyboard.bind(this)); + } + + supportsFile() { + return (window.File ? true : false); + } + + /** + * Some browsers support the file API but don't support the File constructor (new File()). + */ + supportsFileConstructor() { + return typeof File === 'function'; + } + + supportsCssAnimation() { + return this.supportsCssProperty('animation'); + } + + /** + * Used to determine if browser supports full history API. + * Note that IE9 only partially supports the API, pushState and replaceState functions are missing. + * @see: https://developer.mozilla.org/de/docs/Web/API/Window/history + */ + supportsHistoryApi() { + return !!(window.history && window.history.pushState); + } + + supportsCssGradient() { + var testValue = 'linear-gradient(to left, #000 0%, #000 50%, transparent 50%, transparent 100% )'; + return this.supportsFeature('gradient', this.checkCssValue.bind(this, 'backgroundImage', testValue, function(actualValue) { + return (actualValue + '').indexOf('gradient') > 0; + })); + } + + supportsCssUserSelect() { + return this.supportsCssProperty('userSelect'); + } + + supportsInternationalization() { + return window.Intl && typeof window.Intl === 'object'; + } + + /** + * Returns true if the device supports the download of resources in the same window as the single page app is running. + * With "download" we mean: change <code>window.location.href</code> to the URL of the resource to download. Some browsers don't + * support this behavior and require the resource to be opened in a new window with <code>window.open</code>. + */ + supportsDownloadInSameWindow() { + return Browser.FIREFOX !== this.browser; + } + + hasPrettyScrollbars() { + return this.supportsFeature('_prettyScrollbars', function check(property) { + return this.scrollbarWidth === 0; + }.bind(this)); + } + + canHideScrollbars() { + return this.supportsFeature('_canHideScrollbars', function check(property) { + // Check if scrollbar is vanished if class hybrid-scrollable is applied which hides the scrollbar, see also scrollbars.js and Scrollbar.less + return this._detectScrollbarWidth('hybrid-scrollable') === 0; + }.bind(this)); + } + + supportsCopyFromDisabledInputFields() { + return Browser.FIREFOX !== this.browser; + } + + /** + * If the mouse down on an element with a pseudo element removes the pseudo element (e.g. check box toggling), + * the firefox cannot focus the element anymore and instead focuses the body. In that case manual focus handling is necessary. + */ + loosesFocusIfPseudoElementIsRemoved() { + return Browser.FIREFOX === this.browser; + } + + supportsCssProperty(property) { + return this.supportsFeature(property, function check(property) { + if (document.body.style[property] !== undefined) { + return true; + } + + property = property.charAt(0).toUpperCase() + property.slice(1); + for (var i = 0; i < vendorPrefixes.length; i++) { + if (document.body.style[vendorPrefixes[i] + property] !== undefined) { + return true; + } + } + return false; + }); + } + + supportsGeolocation() { + if (navigator.geolocation) { + return true; + } + return false; + } + + /** + * When we call .preventDefault() on a mousedown event Firefox doesn't apply the :active state. + * Since W3C does not specify an expected behavior, we need this workaround for consistent behavior in + * our UI. The issue has been reported to Mozilla but it doesn't look like there will be a bugfix soon: + * + * https://bugzilla.mozilla.org/show_bug.cgi?id=771241#c7 + */ + requiresSyntheticActiveState() { + return this.isFirefox(); + } + + supportsPassiveEventListener() { + return this.supportsFeature('_passiveEventListener', function check(property) { + // Code from MDN https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support + var passiveSupported = false; + try { + var options = Object.defineProperty({}, 'passive', { + get: function() { + passiveSupported = true; + } + }); + window.addEventListener('test', options, options); + window.removeEventListener('test', options, options); + } catch (err) { + passiveSupported = false; + } + return passiveSupported; + }.bind(this)); + } + + checkCssValue(property, value, checkFunc) { + // Check if property is supported at all, otherwise div.style[property] would just add it and checkFunc would always return true + if (document.body.style[property] === undefined) { + return false; + } + var div = document.createElement('div'); + div.style[property] = value; + if (checkFunc(div.style[property])) { + return true; + } + + property = property.charAt(0).toUpperCase() + property.slice(1); + for (var i = 0; i < vendorPrefixes.length; i++) { + var vendorProperty = vendorPrefixes[i] + property; + if (document.body.style[vendorProperty] !== undefined) { + div.style[vendorProperty] = value; + if (checkFunc(div.style[vendorProperty])) { + return true; + } + } + } + return false; + } + + /** + * Returns '' for modern browsers, that support the 'user-select' CSS property. + * Returns ' unselectable="on"' for IE9. + * This string can be used to add to any HTML element as attribute. + */ + getUnselectableAttribute() { + return this.supportsFeature('_unselectableAttribute', function(property) { + if (this.supportsCssUserSelect()) { + return DEFAULT_UNSELECTABLE_ATTRIBUTE; + } + // required for IE 9 + return { + key: 'unselectable', + value: 'on', + string: ' unselectable="on"' + }; + }.bind(this)); + } + + /** + * Returns false for modern browsers, that support CSS table-cell properties restricted with a + * max-width and hidden overflow. Returns true if an additional div level is required (e.g. IE 9). + */ + isTableAdditionalDivRequired() { + return this.supportsFeature('_tableAdditionalDivRequired', function(property) { + var $test = $('body') + .appendDiv() + .text('Scout') + .css('visibility', 'hidden') + .css('display', 'table-cell') + .css('max-width', '1px') + .css('overflow', 'hidden'); + var w = $test.width(); + $test.remove(); + // Expected width is 1px, however this value could be larger when the browser zoom level + // is not set to 100% (e.g. 1.6px). To be on the safe side, we use a threshold of 5px. + // (If max-width is not supported, the width of the test text will be > 30px.) + return (w > 5); + }.bind(this)); + } + + requiresIframeSecurityAttribute() { + return this.supportsFeature('_requiresIframeSecurityAttribute', function(property) { + var test = document.createElement('iframe'); + var supportsSandbox = ('sandbox' in test); + + if (supportsSandbox) { + return false; + } else { + return ('security' in test); + } + }.bind(this)); + } + + _detectScrollbarWidth(cssClass) { + var $measure = $('body') + .appendDiv(cssClass) + .attr('id', 'MeasureScrollbar') + .css('width', 50) + .css('height', 50) + .css('overflow-y', 'scroll'), + measureElement = $measure[0]; + var scrollbarWidth = measureElement.offsetWidth - measureElement.clientWidth; + $measure.remove(); + return scrollbarWidth; + } + + toString() { + return 'scout.Device[' + + 'system=' + this.system + + ' browser=' + this.browser + + ' browserVersion=' + this.browserVersion + + ' type=' + this.type + + ' scrollbarWidth=' + this.scrollbarWidth + + ' features=' + JSON.stringify(this.features) + ']'; + } + + static get() { + return instance; + } +} + +export const DEFAULT_UNSELECTABLE_ATTRIBUTE = { + key: null, + value: null, + string: '' +}; + +const vendorPrefixes = ['Webkit', 'Moz', 'O', 'ms', 'Khtml']; + +export const Browser = { + UNKNOWN: 'Unknown', + FIREFOX: 'Firefox', + CHROME: 'Chrome', + INTERNET_EXPLORER: 'InternetExplorer', + EDGE: 'Edge', + SAFARI: 'Safari' +}; + +export const System ={ + UNKNOWN: 'Unknown', + IOS: 'IOS', + ANDROID: 'ANDROID', + WINDOWS: 'WINDOWS' +}; + +export const Type = { + DESKTOP: 'DESKTOP', + TABLET: 'TABLET', + MOBILE: 'MOBILE' +}; + +App.addListener('prepare', function() { + if (instance) { + // if the device was created before the app itself, use it instead of creating a new one + return; + } + instance = scout.create(Device, { + userAgent: navigator.userAgent + }); +}); |