// This was functionality of script.js, moved it to here to start the downloading of track history earlier "use strict"; console.time("Page Load"); // TAR1090 application object let TAR; TAR = (function (global, jQuery, TAR) { return TAR; }(window, jQuery, TAR || {})); // global object to store big stuff ... avoid clojur stupidity keeping the reference to big objects let g = {}; let loadFinished = false; let Dump1090Version = "unknown version"; let RefreshInterval = 1000; let globeSimLoad = 6; let aggregator = false; let enable_uat = false; let enable_pf_data = false; let HistoryChunks = false; let nHistoryItems = 0; let HistoryItemsLoaded; let HistoryItemsReturned = 0; let chunkNames = []; let PositionHistoryBuffer; var receiverJson; let deferHistory; let historyLoaded = jQuery.Deferred(); let zstdDefer = jQuery.Deferred(); let configureReceiver = jQuery.Deferred(); let historyQueued = jQuery.Deferred(); let historyTimeout = 60; let globeIndex = 0; let globeIndexGrid = 0; let globeIndexSpecialTiles; let binCraft = false; let reApi = false; let zstd = true; // default to on let dbServer = false; let l3harris = false; let heatmap = false; let heatLoaded = 0; let heatmapDefer = jQuery.Deferred(); let heatChunks = []; let heatPoints = []; let replay = false; let rData = []; let StaleReceiverCount = 0; let pTracks = false; let pTracksInterval = 15; let lastTraceGet = 0; let traceRate = 0; let tfrs = false; let initialURL = window.location.href; let milRanges = []; let guessModeS = window.location.href.match(/devg/) ? true : false; let calcOutlineData = null; let uuid = null; let uuidCache = []; let inhibitFetch = false; let zstdDecode = null; let usp; try { // let's make this case insensitive usp = { params: new URLSearchParams(), has: function(s) {return this.params.has(s.toLowerCase());}, get: function(s) { let val = this.params.get(s.toLowerCase()); if (val) { // make XSS a bit harder val = val.replace(/[<>#&]/g, ''); //console.log("usp.get(" + s + ") = " + val); } return val; }, getFloat: function(s) { if (!this.params.has(s.toLowerCase())) return null; const param = this.params.get(s.toLowerCase()); if (!param) return null; const val = parseFloat(param); if (isNaN(val)) return null; return val; }, getInt: function(s) { if (!this.params.has(s.toLowerCase())) return null; const param = this.params.get(s.toLowerCase()); if (!param) return null; const val = parseInt(param, 10); if (isNaN(val)) return null; return val; } }; const inputParams = new URLSearchParams(window.location.search); for (const [k, v] of inputParams) { usp.params.append(k.toLowerCase(), v); } } catch (error) { console.error(error); usp = { has: function() {return false;}, get: function() {return null;}, } } var loStore; // Fake localStorage implementation. // Mimics localStorage, including events. // It will work just like localStorage, except for the persistant storage part. var fakeLocalStorage = function() { var fakeLocalStorage = {}; var storage; // If Storage exists we modify it to write to our fakeLocalStorage object instead. // If Storage does not exist we create an empty object. loStore = {}; storage = loStore; // For older IE if (!window.location.origin) { window.location.origin = window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port: ''); } var dispatchStorageEvent = function(key, newValue) { var oldValue = (key == null) ? null : storage.getItem(key); // `==` to match both null and undefined var url = location.href.substr(location.origin.length); var storageEvent = document.createEvent('StorageEvent'); // For IE, http://stackoverflow.com/a/25514935/1214183 storageEvent.initStorageEvent('storage', false, false, key, oldValue, newValue, url, null); window.dispatchEvent(storageEvent); }; storage.key = function(i) { var key = Object.keys(fakeLocalStorage)[i]; return typeof key === 'string' ? key : null; }; storage.getItem = function(key) { return typeof fakeLocalStorage[key] === 'string' ? fakeLocalStorage[key] : null; }; storage.setItem = function(key, value) { dispatchStorageEvent(key, value); fakeLocalStorage[key] = String(value); }; storage.removeItem = function(key) { dispatchStorageEvent(key, null); delete fakeLocalStorage[key]; }; storage.clear = function() { dispatchStorageEvent(null, null); fakeLocalStorage = {}; }; }; if (window.location.href.match(/aggregator.com/) && window.location.pathname == '/') { aggregator = true; } if (0 && window.self != window.top) { fakeLocalStorage(); } else { try { loStore = window.localStorage; loStore.setItem('localStorageTest', 1); loStore.removeItem('localStorageTest'); } catch (error) { fakeLocalStorage(); /* const splat = "Your browser isn't supporting localStorage.\nSafari / Apple: turn off \"Block Cookies\"!"; jQuery("#js_error").text(splat); jQuery("#js_error").css('display','block'); throw 'FATAL, your browser does not support localStorage!'; */ } } let firstError = true; if (usp.has('showerrors') || usp.has('jse')) { window.onerror = function (msg, url, lineNo, columnNo, error) { if (firstError) { firstError = false; let splat = ''; splat += 'Uncaught JS Error:' + url + ' line ' + lineNo + '\n'; splat += msg + '\n'; if (error && error.stack) splat += '\n' + error.stack; jQuery("#js_error").text(splat); jQuery("#js_error").css('display','block'); } return false; } } else { window.onerror = function (msg, url, lineNo, columnNo, error) { return false; } } function resetSettings() { loStore.clear(); if (window.history && window.history.replaceState) { window.history.replaceState("object or string", "Title", window.location.pathname); location.reload(); } } if (usp.has('reset')) { resetSettings(); } const feed = usp.get('feed'); if (feed != null) { console.log('feed: ' + feed); let split = feed.split(','); if (split.length > 0) { uuid = []; for (let i in split) { uuid.push(encodeURIComponent(split[i])); } if (uuid[0].length > 18 && window.location.href.match(/adsbexchange.com/)) { console.log('redirecting the idiot, oui!'); let URL = 'https://www.adsbexchange.com/api/feeders/tar1090/?feed=' + uuid[0]; console.log(URL); //window.history.pushState(URL, "Title", URL); window.location.href = URL; } } else { console.error('uuid / feed fail!'); } } if (usp.has('tfrs')) { tfrs = true; } let uk_advisory = false; if (usp.has('uk_advisory')) { uk_advisory = true; } const customTiles = usp.get('customTiles'); if (customTiles) loStore['customTiles'] = customTiles; if (customTiles == 'remove') loStore.removeItem('customTiles'); const bingKey = usp.get('BingMapsAPIKey'); if (bingKey) loStore['bingKey'] = bingKey; if (bingKey == 'remove') loStore.removeItem('bingKey'); if (usp.has('l3harris') || usp.has('ift')) { l3harris = true; } if (usp.has('r') || usp.has('replay')) { replay = true; } function arraybufferRequest() { let xhrOverride = new XMLHttpRequest(); xhrOverride.responseType = 'arraybuffer'; return xhrOverride; } if (usp.has('heatmap') || usp.has('realHeat')) { heatmap = {}; heatmap.max = 32000; heatmap.init = true; heatmap.duration = 24; heatmap.end = (new Date()).getTime(); let tmp = parseFloat(usp.get('heatDuration')); if (!isNaN(tmp)) heatmap.duration = tmp; if (heatmap.duration < 0.5) heatmap.duration = 0.5; tmp = parseFloat(usp.get('heatEnd')); if (!isNaN(tmp)) heatmap.end -= tmp * 3600 * 1000; if (usp.has('heatLines')) heatmap.lines = true; if (usp.has('heatfilters')) heatmap.filters = true; tmp = parseFloat(usp.get('heatAlpha')); if (!isNaN(tmp)) { heatmap.alpha = tmp; console.log('heatmap.alpha = ' + tmp); } heatmap.radius = 2.5; if (usp.has('realHeat')) { heatmap.max = 50000; heatmap.real = true; heatmap.radius = 1.5; heatmap.blur = 4; heatmap.weight = 0.25; tmp = parseFloat(usp.get('heatBlur')); if (!isNaN(tmp)) heatmap.blur = tmp; tmp = parseFloat(usp.get('heatWeight')); if (!isNaN(tmp)) heatmap.weight = tmp; } tmp = parseFloat(usp.get('heatRadius')); if (!isNaN(tmp)) heatmap.radius = tmp; let val; if (val = parseInt(usp.get('heatmap'), 10)) heatmap.max = val; if (usp.has('heatManualRedraw')) heatmap.manualRedraw = true; } { let value; if ((value = usp.getFloat('pTracksInterval')) != null) { pTracksInterval = value; } } if (usp.has('pTracks')) { let tmp = parseFloat(usp.get('pTracks')) if (tmp > 0 && tmp < 9999) pTracks = tmp; else pTracks = 9999; } function getDay(date) { if ((utcTimesLive && !showTrace) || (utcTimesHistoric && showTrace)) return date.getUTCDate(); else return date.getDate(); } function zuluTime(date) { return date.getUTCHours().toString().padStart(2,'0') + ":" + date.getUTCMinutes().toString().padStart(2,'0') + ":" + date.getUTCSeconds().toString().padStart(2,'0'); } const TIMEZONE = new Date().toLocaleTimeString(undefined,{timeZoneName:'short'}).split(' ')[2]; function localTime(date) { return date.getHours().toString().padStart(2,'0') + ":" + date.getMinutes().toString().padStart(2,'0') + ":" + date.getSeconds().toString().padStart(2,'0'); } function zDateString(date) { let string = date.getUTCFullYear() + '-' + (date.getUTCMonth() + 1).toString().padStart(2, '0') + '-' + date.getUTCDate().toString().padStart(2, '0') return string; } function sDateString(date) { let string = date.getUTCFullYear() + '/' + (date.getUTCMonth() + 1).toString().padStart(2, '0') + '/' + date.getUTCDate().toString().padStart(2, '0') return string; } function lDateString(date) { let string = date.getFullYear() + '-' + (date.getMonth() + 1).toString().padStart(2, '0') + '-' + date.getDate().toString().padStart(2, '0') return string; } function chunksDefer() { return jQuery.ajax({ url:'chunks/chunks.json', cache: false, dataType: 'json', timeout: 4000, }); } function handleJsonWorker(e) { const url = e.data.url; //console.log("url finished: " + url); const defer = g.jwr[url]; delete g.jwr[url]; defer.resolve(e.data.json); }; function jsonGetWorker(url) { const defer = jQuery.Deferred(); g.jwr[url] = defer; const wid = g.jsonGetId++ % g.jsonWorker.length; //console.log(`using worker ${wid}`); g.jsonWorker[wid].postMessage(url); return defer; } g.jWorkers = 0; if (g.jWorkers) { g.jwr = {}; g.jsonWorker = []; g.jsonGetId = 0; for (let i = 0; i < g.jWorkers; i++) { const worker = new Worker("jsonWorker.js"); g.jsonWorker.push(worker); worker.onmessage = handleJsonWorker; } } let get_receiver_defer; let test_chunk_defer; const hostname = window.location.hostname; if (uuid) { // don't need receiver / chunks json } else if (aggregator) { console.log("Using aggregator fast-path load!"); let data = {"zstd":true,"reapi":true,"refresh":1000,"history":1,"dbServer":true,"binCraft":true,"globeIndexGrid":3,"globeIndexSpecialTiles":[],"version":"aggregator backend"}; get_receiver_defer = jQuery.Deferred().resolve(data); test_chunk_defer = jQuery.Deferred().reject(); } else { // get configuration json files, will be used in initialize function {get_receiver_defer = jQuery.ajax({ url: 'data/receiver.json', cache: false, dataType: 'json', timeout: 10000, });} {test_chunk_defer = chunksDefer();} } {jQuery.getJSON(databaseFolder + "/ranges.js").done(function(ranges) { if (!ranges || !ranges.military) { console.error("couldn't load milRanges."); return; } for (let i in ranges.military) { const r = ranges.military[i]; const a = +("0x" + r[0]); const b = +("0x" + r[1]); if (isNaN(a) || isNaN(b)) continue; milRanges.push([a, b]); } });} let heatmapLoadingState = {}; function loadHeatChunk() { if (heatmapLoadingState.index > heatChunks.length) { heatmapDefer.resolve(); return; // done, stop recursing } let time = new Date(heatmapLoadingState.start + heatmapLoadingState.index * heatmapLoadingState.interval); let sDate = sDateString(time); let index = 2 * time.getUTCHours() + Math.floor(time.getUTCMinutes() / 30); let base = "globe_history/"; let URL = base + sDate + "/heatmap/" + index.toString().padStart(2, '0') + ".bin.ttf"; let req = jQuery.ajax({ url: URL, method: 'GET', num: heatmapLoadingState.index, xhr: arraybufferRequest, }); {req.done(function (responseData) { heatChunks[this.num] = responseData; loadHeatChunk(); });} {req.fail(function(jqxhr, status, error) { loadHeatChunk(); });} heatmapLoadingState.index++; } if (!heatmap) { heatmapDefer.resolve(); } else { let end = heatmap.end; let start = end - heatmap.duration * 3600 * 1000; // timestamp in ms let interval = 1800 * 1000; let numChunks = Math.round((end - start) / interval); console.log('numChunks: ' + numChunks + ' heatDuration: ' + heatmap.duration + ' heatEnd: ' + new Date(heatmap.end)); heatChunks = Array(numChunks).fill(null); heatPoints = Array(numChunks).fill(null); // load chunks sequentially via recursion: heatmapLoadingState.index = 0; heatmapLoadingState.interval = interval; heatmapLoadingState.start = start; // 2 async chains of heat chunk loading: loadHeatChunk(); loadHeatChunk(); } if (uuid != null) { receiverJson = null; Dump1090Version = 'unknown'; RefreshInterval = 5000; configureReceiver.resolve(); //console.time("Downloaded History"); zstd = false; init_zstddec(); } else { get_receiver_defer.fail(function(data){ setTimeout(function() { jQuery("#loader").addClass("hidden"); jQuery("#update_error_detail").text("Seems the decoder / receiver / backend isn't working correctly!"); jQuery("#update_error").css('display','block'); }, 2000); setTimeout(function() { location.reload(); }, 10000); }); get_receiver_defer.done(function(data){ receiverJson = data; Dump1090Version = data.version; RefreshInterval = data.refresh; nHistoryItems = (data.history < 2) ? 0 : data.history; binCraft = data.binCraft ? true : false || data.aircraft_binCraft ? true : false; zstd = data.zstd; reApi = data.reapi ? true : false; if (usp.has('noglobe') || usp.has('ptracks')) { data.globeIndexGrid = null; // disable globe on user request } dbServer = (data.dbServer) ? true : false; if (heatmap || replay) { if (replay && data.globeIndexGrid != null) globeIndex = 1; HistoryChunks = false; nHistoryItems = 0; get_history(); } else if (data.globeIndexGrid != null) { HistoryChunks = false; nHistoryItems = 0; globeIndex = 1; if (receiverJson.globeIndexGrid) { globeIndexGrid = receiverJson.globeIndexGrid; globeIndex = 1; globeIndexSpecialTiles = []; for (let i = 0; i < receiverJson.globeIndexSpecialTiles.length; i++) { let tile = receiverJson.globeIndexSpecialTiles[i]; globeIndexSpecialTiles.push([tile.south, tile.west, tile.north, tile.east]); } } get_history(); } else { test_chunk_defer.done(function(data) { HistoryChunks = true; console.log("Chunks enabled!"); chunkNames = (pTracks ? data.chunks_all : data.chunks) || []; nHistoryItems = chunkNames.length; enable_uat = (data.enable_uat == "true"); enable_pf_data = (data.pf_data == "true"); if (enable_uat) console.log("UAT/978 enabled!"); get_history(); }).fail(function() { HistoryChunks = false; get_history(); }); } init_zstddec(); }); } function get_history() { if (!loadFinished) { if (!globeIndex && !uuid) { let request = jQuery.ajax({ url: 'upintheair.json', cache: true, dataType: 'json' }); request.done(function(data) { calcOutlineData = data; }); request.always(function() { configureReceiver.resolve(); }); } else { configureReceiver.resolve(); } } deferHistory = []; HistoryItemsLoaded = 0; if (nHistoryItems > 0) { jQuery("#loader_progress").attr('max',nHistoryItems + 1); console.time("Downloaded History"); nHistoryItems++; let request = jQuery.ajax({ url: 'data/aircraft.json', timeout: historyTimeout*800, cache: false, dataType: 'json' }); deferHistory.push(request); if (enable_uat) { nHistoryItems++; request = jQuery.ajax({ url: 'chunks/978.json', timeout: historyTimeout*800, cache: false, dataType: 'json' }); deferHistory.push(request); } if (HistoryChunks) { //console.log("Starting to load history (" + nHistoryItems + " chunks)"); for (let i = chunkNames.length-1; i >= 0; i--) { get_history_item(i); } } else { //console.log("Starting to load history (" + nHistoryItems + " items)"); for (let i = nHistoryItems-1; i >= 0; i--) { get_history_item(i); } } } historyQueued.resolve(); } function get_history_item(i) { let request; if (HistoryChunks) { const url = 'chunks/' + chunkNames[i]; if (g.jWorkers) { request = jsonGetWorker(url); } else { request = jQuery.ajax({ url: url, timeout: historyTimeout * 1000, dataType: 'json' }); } } else { request = jQuery.ajax({ url: 'data/history_' + i + '.json', timeout: nHistoryItems * 80, // Allow 40 ms load time per history entry cache: false, dataType: 'json' }); } request .done(function(json) { jQuery("#loader_progress").attr('value',++HistoryItemsLoaded); }) .fail(function(jqxhr, status, error) { jQuery("#loader_progress").attr('value',++HistoryItemsLoaded); }); deferHistory.push(request); } function getCookie(cname) { let name = cname + "="; let ca = decodeURIComponent(document.cookie).split(';'); for(let i = 0; i < ca.length; i++) { let c = ca[i]; while (c.charAt(0) == ' ') { c = c.substring(1); } if (c.indexOf(name) == 0) { return c.substring(name.length, c.length); } } return ""; } function setCookie(cname, cvalue, exdays) { let d = new Date(); d.setTime(d.getTime() + (exdays*24*60*60*1000)); let expires = "expires="+ d.toUTCString(); document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/"; } const toggles = {}; function Toggle(arg) { this.key = arg.key; this.state = (arg.init ? true : false); this.setState = arg.setState; this.checkbox = (arg.checkbox == undefined) ? ('#' + this.key + '_cb') : null; this.display = arg.display; this.container = arg.container; this.button = arg.button || this.checkbox; toggles[this.key] = this; this.init(); } Toggle.prototype.init = function() { if (this.container) { jQuery(this.container).append(( '
' + '
' + '
' + this.display + '
' + '
')); } if (this.button) { jQuery(this.button).on('click', () => {this.toggle()}); } if (loStore[this.key] == 'true') this.state = true; if (loStore[this.key] == 'false') this.state = false this.toggle(this.state, true); } Toggle.prototype.toggle = function(override, init) { if (override == true) this.state = true; else if (override == false) this.state = false; else this.state = !this.state; if (this.setState) { if (this.setState(this.state) == false) { this.state = !this.state; return; } } if (this.checkbox) { if (this.state == false) { jQuery(this.checkbox).removeClass('settingsCheckboxChecked'); } else { jQuery(this.checkbox).addClass('settingsCheckboxChecked'); } } if (!init) loStore[this.key] = this.state; } Toggle.prototype.restore = function () { if (this.setState) this.setState(this.state); } Toggle.prototype.hideCheckbox = function () { if (this.checkbox) jQuery(this.checkbox).parent().hide(); } // From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys if (!Object.keys) { Object.keys = (function() { 'use strict'; var hasOwnProperty = Object.prototype.hasOwnProperty, hasDontEnumBug = !({ toString: null }).propertyIsEnumerable('toString'), dontEnums = [ 'toString', 'toLocaleString', 'valueOf', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', 'constructor' ], dontEnumsLength = dontEnums.length; return function(obj) { if (typeof obj !== 'function' && (typeof obj !== 'object' || obj === null)) { throw new TypeError('Object.keys called on non-object'); } var result = [], prop, i; for (prop in obj) { if (hasOwnProperty.call(obj, prop)) { result.push(prop); } } if (hasDontEnumBug) { for (i = 0; i < dontEnumsLength; i++) { if (hasOwnProperty.call(obj, dontEnums[i])) { result.push(dontEnums[i]); } } } return result; }; }()); } if (!Object.entries) { Object.entries = function( obj ){ var ownProps = Object.keys( obj ), i = ownProps.length, resArray = new Array(i); // preallocate the Array while (i--) resArray[i] = [ownProps[i], obj[ownProps[i]]]; return resArray; }; } let custom_layers = new ol.Collection(); function add_kml_overlay(url, name, opacity) { custom_layers.push(new ol.layer.Vector({ source: new ol.source.Vector({ url: url, format: new ol.format.KML(), }), name: name, title: 'custom_' + name, type: 'overlay', opacity: opacity, visible: true, zIndex: 99, })); } function webAssemblyFail(e) { zstdDecode = null; zstd = false; if (!reApi) { binCraft = false; } // this enforcing should not be needed if (0 && aggregator && !uuid) { inhibitFetch = true; reApi = false; jQuery("#generic_error_detail").text("Your browser is not supporting webassembly, this website does not work without webassembly."); jQuery("#generic_error").css('display','block'); } if (e) { console.log(e); } console.error("Error loading zstddec, probable cause: webassembly not present or not working"); } function init_zstddec() { if (!zstd) { zstdDefer.resolve(); return; } try { zstddec.decoder = new zstddec.ZSTDDecoder(); zstddec.promise = zstddec.decoder.init(); zstdDecode = zstddec.decoder.decode; } catch (e) { webAssemblyFail(e); zstdDefer.resolve(); return; } if (!zstdDecode) { zstdDefer.resolve(); } else { try { zstddec.promise.then(function() { zstdDefer.resolve(); }); } catch (e) { webAssemblyFail(e); zstdDefer.resolve(); } } } let onMobile = ( () => { let a = (navigator.userAgent||navigator.vendor||window.opera); if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) { return true; } else { return false; } } )();