basti1012.bplaced.net

Du siehst gerade eine vereinfachte Darstellung unserer Inhalte. Normale Ansicht mit richtiger Formatierung.
    Das kann jeder Webseiten Betreiber über dich auslesen

    Code

                                        
                                    <main>
    <style>
    @import url("https://fonts.googleapis.com/css2?family=Open+Sans:wght@800&display=swap");
    @import url("https://fonts.googleapis.com/css2?family=Heebo:wght@900&display=swap");
    * {
      box-sizing: border-box;
    }
    
    
    .card {
      min-height: 100%;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
    }
    .card > * {
      max-width: 1024px;
      margin: 0 auto;
    }
    .card.card__01 {
      background-color: black;
      color: white;
      padding: 0 2rem;
    }
    .card.card__01 h3 {
      text-align: center;
    }
    .card.card__02 {
      background-color: #0d0d0d;
      color: #00c4ff;
      padding-top: 4rem;
      padding-bottom: 4rem;
    }
    .card.card__02 h1 {
      -webkit-text-stroke-width: 1px;
      -webkit-text-stroke-color: #00c4ff;
      color: #0d0d0d;
    }
    .card.card__03 {
      background-color: black;
    }
    .card h2 {
      font-family: "Opens Sans", sans-serif;
      margin-top: 2rem;
    }
    .card h1 {
      font-family: "Open Sans", sans-serif;
      font-weight: 700;
      -webkit-text-stroke-width: 1px;
      -webkit-text-stroke-color: white;
      color: black;
      letter-spacing: 2px;
    }
    .card h3 {
      font-family: "Comfortaa", cursive;
    }
    
    #map {
      height: 100vh;
      background-color: black;
    }
    
    h1 {
      font-size: 36px;
      text-align: center;
    }
    
    ul {
      margin: 0;
      padding-left: 1.25rem;
    }
    ul li {
      font-family: "Comfortaa", cursive;
    }
    
    .geolocation,
    .ipinfo {
      display: none;
    }
    
    .grid {
        display: flex;
        /* flex-direction: column; */
        /* grid-gap: 2rem; */
        /* padding: 2rem; */
        flex-wrap: wrap;
        align-content: flex-start;
        justify-content: space-around;
        align-items: center;
    }
    .grid > div {
      padding: 0.75rem 2rem 2rem 2rem;
      background: #1a1a1a;
      border-radius: 10px;
    }
    .grid > div {
        padding: 0.75rem 2rem 2rem 2rem;
        background: #1a1a1a;
        min-height: 250px;
        margin: 10px;
        min-width: 250px;
        border-radius: 10px;
    }
    .logo {
      margin-top: 2rem;
    }
    
    .logo img {
      width: 100%;
    }
    
    .map {
      position: relative;
    }
    
    .map__address {
      position: absolute;
      top: 10px;
      right: 4rem;
      z-index: 2;
      background: rgba(255, 255, 255, 0.75);
      padding: 4px;
      width: 35%;
      box-shadow: 0 0 10px 0px rgba(0, 0, 0, 0.5);
    }
    .map__address .map__field {
      padding: 7px 12px;
      width: 100%;
    }
    </style>
    
    
    
    <div class="card card__01">
      <h1>Device Manager</h1>
      <h2>User Agent</h2>
      <h3 id="useragent"></h3>
    </div>
    
    <div class="card card__02">
      <h1>Device Manager Details</h1>
      <div class="logo">
        <img alt="HTML5 CSS JS" src="/image/1499794874html5-js-css3-logo-png.png">
      </div>
      <div class="grid">
        <div class="device">
          <h3>Device</h3>
          <ul>
            <li>Device Name: <span class="device__name"></span></li>
            <li>Device Width: <span class="device__width"></span></li>
            <li>Device Height: : <span class="device__height"></span></li>
            <li>Device Type: <span class="device__type"></span></li>
            <li>Device Orientation: <span class="device__orientation"></span></li>
          </ul>
        </div>
        <div class="browser">
          <h3>Browser</h3>
          <ul>
            <li>Browser Name: <span class="browser__name"></span></li>
            <li>Browser Cookie Enabled: <span class="browser__cookieEnabled"></span></li>
            <li>Browser Width: <span class="browser__width"></span></li>
            <li>Browser Height: : <span class="browser__height"></span></li>
            <li>Browser Version: : <span class="browser__version"></span></li>
            <li>Browser Scroll Top: <span class="browser__scrollTop"></span></li>
            <li>Browser Scroll Left: <span class="browser__scrollLeft"></span></li>
          </ul>
        </div>
        <div class="os">
          <h3>Operating System</h3>
          <ul>
            <li>OS Name: <span class="os__name"></span></li>
            <li>OS Language: <span class="os__language"></span></li>
            <li>OS Platform: <span class="os__platform"></span></li>
            <li>OS Version: <span class="os__version"></span></li>
          </ul>
        </div>
        <div class="ipinfo">
          <h3>IP Info</h3>
          <ul>
            <li>IP Info Country: <span class="browser__ipinfo__country"></span></li>
            <li>IP Info City: <span class="browser__ipinfo__city"></span></li>
            <li>IP Info Postal: <span class="browser__ipinfo__postal"></span></li>
            <li>IP Info Region: <span class="browser__ipinfo__region"></span></li>
            <li>IP Info Host Name: <span class="browser__ipinfo__hostname"></span></li>
            <li>IP Info Oranization: <span class="browser__ipinfo__org"></span></li>
            <li>IP Info IP: <span class="browser__ipinfo__ip"></span></li>
            <li>IP Info LOC: <span class="browser__ipinfo__loc"></span></li>
            <li>IP Info Timezone: <span class="browser__ipinfo__timezone"></span></li>
          </ul>
        </div>
        <div class="user_info">
          <h3>User Info</h3>
          <ul>
            <li>First Name: <span id="first_name" class="user__first_name"></span></li>
            <li>Last Name: <span id="last_name" class="user__las_tname"></span></li>
            <li>Full Name: <span id="full_name" class="user__full_name"></span></li>
            <li>Gender: <span  id="gender" class="user__gender"></span></li>
            <li>Street: <span id="street_number" class="user__address__street_number"></span></li>
            <li>Route: <span id="route" class="user__address__route"></span></li>
            <li>City: <span id="locality" class="user__address__locality"></span></li>
            <li>State: <span id="administrative_area_level_1" class="user__address__administrative_area_level_1"></span></li>
            <li>Postal Code: <span class="user__address__postal_code"></span></li>
            <li>Country: <span id="country" class="user__address__country"></span></li>
          </ul>
        </div>
        <div class="geolocation">
          <h3>Geo-Location</h3>
          <ul>
            <li>Geolocation Heading: <span class="geolocation__heading"></span></li>
            <li>Geolocation Altitude Accuracy: <span class="geolocation__altitudeAccuracy"></span></li>
            <li>Geolocation Accuracy: <span class="geolocation__accuracy"></span></li>
            <li>Geolocation Altitude: <span class="geolocation__altitude"></span></li>
            <li>Geolocation Latitude: <span class="geolocation__latitude"></span></li>
            <li>Geolocation Longitude: <span class="geolocation__longitude"></span></li>
            <li>Geolocation Speed: <span class="geolocation__speed"></span></li>
          </ul>
        </div>
        <div class="cookies">
          <h3>Cookies</h3>
          <ul class="cookies__list">
          </ul>
          <h3>History</h3>
          <ul class="history__list">
          </ul>
          <h3>Favorites</h3>
          <ul class="favorites__list">
          </ul>
        </div>
      </div>
    
    </div>
    <div id="viewDiv"></div>
    <div class="map">
      <div class="map__address">
        <form action="#" autocomplete="false">
        <input type="text" name="search_address" id="search__address"  class="map__field" placeholder="Search For Address..."  autocomplete="false" readonly onfocus="this.removeAttribute('readonly')">
        </form>
      </div>
      <div id="map"></div>
    </div>
    
    <script>
      
      sys = {};
    sys.ui = {};
    sys.ui.device = {};
    sys.ui.device.cookie = {};
    sys.cookie = (...args) => { return Cookie(...args) };
    sys.device = (...args) => { return new DeviceManager(...args) };
    sys.plugin = (...args) => {
      
      switch(args[0]) {
        case 'browser':
          return new BrowserPlugin();  
          break;
          
        case 'history':
          return new HistoryPlugin();  
          break;
          
        case 'favorites':
          return new FavoritesPlugin();  
          break;
      }
    };
    
    function Cookie(...args) {
      let cookie = { };
      
      const checkType = o => Object.prototype.toString.call(o).replace(/\[|object\s|\]/g, '').toLowerCase();
    
      const getCookie = (name) => {
    
        var arr = document.cookie.split("; ");
        
        // Loop through the array elements
        for(var i = 0; i < arr.length; i++) {
            var pair = arr[i].split("=");
            
            if(cookie.name == pair[0].trim()) {
                return document.cookie;
            }
        }
        
        return localStorage.getItem(cookie.name);
      } 
      
      try {
        
        switch(args.length - 1) {
          case 4:    
            // params = name, expires, path, domain, object 
            if(args[4].constructor === {}.constructor) {
              cookie.obj = args[4];
            } else {
              throw new Error('Cookie value must be of type object containing "data" and "dataType" with optional "url".');
            }             
    
            if(args[3].match(new RegExp(/^((?:(?:(?:\w[\.\-\+]?)*)\w)+)((?:(?:(?:\w[\.\-\+]?){0,62})\w)+)\.(\w{2,6})$/))) {
              cookie.domain = args[3];
            } else if(args[3] == null) {
              cookie.domain = document.domain;
            } else {
              throw new Error('Cookie domain is invalid.');
            }              
    
            if(args[2].indexOf('/') > -1) {
              cookie.path = args[2];
            } else {
              throw new Error('Cookie path is invalid.');
            }
    
            if(typeof args[1] == 'string' || (isDate(args[1]) || args[1] == 'now')) {
              cookie.expires = args[1];
            } else if(args[1] == null) {
              cookie.expires = new Date();
            } else {
              throw new Error('Cookie expirary date is invalid.');
            }
    
            if(args[0] != null || args[0] != undefined || typeof args[0] == 'string') {
              cookie.name = args[0];
            } else {
              throw new Error('Cookie name is invalid.');
            }
    
            break;
          case 2:
            // params = name, expires, object
            if(args[4].constructor === {}.constructor) {
              cookie.obj = args[2];
            } else {
              throw new Error('Cookie value must be of type object containing "data" and "dataType" with optional "url".');
            }
    
            if(typeof args[1] == 'string' || isDate(args[1]) || args[1] == 'now') {
              cookie.expires = args[1];
            } else if(args[1] == null) {
              cookie.expires = new Date();
            } else {
              throw new Error('Cookie expirary date is invalid.');
            }              
    
            if(args[0] != null || args[0] != undefined || typeof args[0] == 'string') {
              cookie.name = args[0];
            } else {
              throw new Error('Cookie must contain a name');
            }
    
            cookie.path = '/';
            cookie.domain = document.domain;
            break;
            
          case 1:
            
            if(args[4].constructor === {}.constructor) {
              cookie.obj = args[1];
            } else{
              throw new Error('Cookie value must be of type object containing "data" and "dataType" with optional "url".');
            }
    
            if(args[0] != null || args[0] != undefined || typeof args[0] == 'string') {
              cookie.name = args[0];  
            } else {
              throw new Error('Cookie must contain a name');
            }              
    
            cookie.path = '/';
            cookie.domain = document.domain;
            cookie.expires = (d => d.setFullYear(d.getFullYear() + 1))(new Date);
            break;
            
          case 0:
            
            if(args[0] != null || args[0] != undefined) {
              cookie.name = args[0];
            } else {
              throw new Error('Cookie must contain a name.');
            }
            // get cookie function
            break;
            
          default:
            if(args[0] == null || args[0] == undefined || args.length == -1 || typeof args[0] != 'string') {
              throw new Error('Cookie constructor must contain a cookie name');
            } else {
              cookie.name = args[0];
            }
        }
        
        let documentCookie = getCookie(cookie.name);
        
        if(args.length - 1 > 0 && (typeof args[1] == 'object' || typeof args[2] == 'object' || typeof args[4] == 'object')) {
          sys.ui.device.cookie[this.name] = new DeviceCookie(cookie);
          return sys.ui.device.cookie[this.name];
        } else if(documentCookie) {
          sys.ui.device.cookie[this.name] = new DeviceCookie(cookie, documentCookie);;
          return sys.ui.device.cookie[this.name];
        }
        
      } catch(e) {
        throw e;
      }
      
      return null;
    }
    
    class DeviceCookie {
      #expiresNow;
      
      constructor(...args) {
        this.name = null;
        this.expires = null;
        this.#expiresNow = false;
        this.path = null;
        this.domain = null;
        this.data = null;
        this.dataType = null;
        this.url = null;
        this.status = null;
        this.value = null;
        
        if(args.length - 1 == 0 && args[0].obj == undefined) {
          this.#rebuildCookie(args[0]);
        } else if(args.length - 1 == 1) {
          this.#buildCookie(args[0], args[1])
        } else {
          console.log('create cookie')
          this.#createCookie(args[0]);
        }
        
        localStorage.setItem(this.name, this.value);
      }
      
      #rebuildCookie(cookie) {
        this.data = cookie.data;
        this.dataType = cookie.dataType;
        this.path = cookie.path;
        this.domain = cookie.domain;
        this.expires = new Date(cookie.expires)
        this.name = cookie.name;
        this.url = cookie.url;
        this.value = cookie.value;
      }
      
      #buildCookie(cookie, documentCookie) {
        
        let items = documentCookie.split('; '),
            cookieDomain = null,
            cookieEpiraryDate = null,
            cookiePath = null,
            data = null,
            dataType = null;
        
        items.forEach((item, index) => {
          if(item.includes(cookie.name)) {
            this.name = cookie.name;
            this.value = documentCookie;
            data = item.replace(cookie.name + '=', '');
          } else {
            
            item = item.replace(';', '')
                       .replace(' ', '')
                       .split('=');
            
            switch(item[0]) {
              case 'domain':
                this.domain = item[0];
                break;
                
              case 'path':
                this.path = item[1];
                break;
    
              case 'expires':
                this.#expiresNow = false;
                this.expires = new Date(item[1]);                
                break;
    
              case 'dataType':
                this.dataType = item[1];
                dataType = item[1];
                break;
    
              case 'url':
                console.log('url', item[1])
                this.url = item[1];  
                break;  
            }
          }    
        });
        
        
        switch(dataType) {
          case 'json':
            this.data = JSON.parse(decodeURIComponent(data));
            break;
            
          case 'array':
            this.data = this.#serializeStringToArray(data);
            break;
        }
        
        this.#getStatus(this.expires);
      }
      
      #convertArrayToJSON(arr) {
        let obj = {};
        let objKeys = {...arr};
    
        const f = (arr) => {
          let obj = {};
          let objKeys = {...arr};
    
          for(const key in objKeys) {
            if(/[A-Za-z]/.test(key) && Array.isArray(objKeys[key])) {
              obj[key] = f(objKeys[key]);
            } else {
              obj[key] = objKeys[key];
            }
          }
    
          return obj;
        }
    
        for(const key in objKeys) {
    
          if(/[A-Za-z]/.test(key) && Array.isArray(objKeys[key])) {
            obj[key] = f(objKeys[key])
          } else {
            obj[key] = objKeys[key];
          }
        }
    
        return obj;
      }
      
      #createCookie(cookie) {
        let value = null;
        
        try {
          
          if(cookie.obj.data == null) {
            throw new Error('Cookie data is undefined.');
          }
          
          if(cookie.obj.dataType == null) {
            throw new Error('Cookie data type is undefined.')
          }
          
          switch(cookie.obj.dataType) {
            case 'json':
              if(this.#isJSONObject(cookie.obj.data)) {
                value = encodeURIComponent(JSON.stringify(cookie.obj.data));  
              } else {
                throw new Error('Cookie data is not a json object.')
              }
              
              break;
            case 'array':
              value = this.#serializeArrayToString(data);
              break;
          }
    
          if(cookie.expires == null) {
            throw new Error('The cookie expirary date is required.')
          }
          
          if(cookie.expires == 'now') {
            this.#expiresNow = true;
            this.expires = this.#createExpiraryDate(new Date());
          } else if(cookie.expires instanceof Date) {
            this.expires = cookie.expires;        
          } else if(typeof cookie.expires == 'string') {
            this.expires = this.#createExpiraryDate(cookie.expires);
          }
          
          this.name = cookie.name;
          this.path = cookie.path;
          this.domain = cookie.domain;
          this.data = cookie.obj?.data || null;
          this.dataType = cookie.obj?.dataType || null;
          this.url = cookie.obj?.url || null;
          this.value = `${cookie.name}=${value}; expires=${this.expires.toUTCString()}; path=${this.path}; domain=${this.domain}; dataType=${this.dataType};`;
          
          if(cookie.obj?.url) {
            this.value += ` url=${cookie.obj.url};`;
          }
          
          this.#getStatus(cookie.expires);
          
        } catch(e) {
          throw e;
        }
      }
      
      #createExpiraryDate(value) {
        let expiraryDate = null;
    
        if(Date.parse(value)) {
          expiraryDate = new Date(value);
        } else if(value != undefined) {
          let parts = value.split(' ');
          let offsetType = parts[1];
          let offset = parseInt(parts[0].substring(1, parts[0].length), 10);
          let oldDate = this.expires != null ? this.expires : new Date();
          let year = parseInt(oldDate.getFullYear());
          let month = parseInt(oldDate.getMonth());
          let date = parseInt(oldDate.getDate());
          let hour = parseInt(oldDate.getHours());
    
          switch(offsetType) {
            case 'minutes':
              expiraryDate = new Date(Date.now() + (offset * 60 * 1000));
              break;
            case "hours":
              var o = oldDate.getTime();
              var n = o + (offset * 60 * 60 * 1000);
    
              expiraryDate = new Date(n);
              break;            
            case 'days':
              var o = oldDate.getTime();
              var n = o + offset * 24 * 3600 * 1000;
    
              expiraryDate = new Date(n);
              break;
            case 'months':
              var yearOffset = 0;
              var monthOffset = 0;
    
              if (offset < 12)
              {
                yearOffset = Math.floor((month + offset) / 12);
                monthOffset = (month + offset) % 12;
              } else {
                yearOffset = Math.floor(offset / 12);
                monthOffset = month % 12 + offset % 12;
              }
    
              expiraryDate = new Date(year + yearOffset, month + monthOffset, date, hour);
              break;
            case 'years':
              expiraryDate = new Date(year + offset, month, date, hour);            
              break;
            default:
              expiraryDate = new Date(year + offset, month, date, hour);
          }
        }
    
        return expiraryDate; 
      };
      
      destroy() {
        let cookie = localStorage.getItem(this.name)
        
        if(cookie != null) {
          localStorage.removeItem(this.name);
          
          delete sys.ui.device.cookie[this.name];
          
          this.name = null;
          this.expires = null;
          this.#expiresNow = false;
          this.path = null;
          this.domain = null;
          this.data = null;
          this.dataType = null;
          this.url = null;
          this.status = null;
          this.value = null;
          
          return true;  
        }
        
        return false;
      }
      
      #isJSONObject(o) { 
        if (o.constructor === {}.constructor) {
            return true;
        }
        
        return false;
      }
      
      #isJSONObjectString(s) {
        try {
          const o = JSON.parse(s);
          return !!o && (typeof o === 'object') && !Array.isArray(o)
        } catch {
          return false
        }
      }
      
      getKeys() {
        let arr = [];
        let keys = localStorage.getItem(this.name)
                   .replace(/((?:^|\s*;)[^\=]+)(?=;|$)|^\s*|\s*(?:\=[^;]*)?(?:\1|$)/g, "")
                   .trim()
                   .split(/\s*(?:\=[^;]*)?;\s*/);
        
        for (var len = keys.length - 1, i = 0; i <= len; i++) {
          if(decodeURIComponent(keys[i]) != "") {
            arr[i] = decodeURIComponent(keys[i]);   
          }
        }
    
        return arr;
      }
      
      #getStatus(expiraryDate) {
        
        let endDate = this.#expiresNow == true ? new Date() : expiraryDate;
        let diff = this.#getDateDifference(new Date(), endDate);
        
        this.status = `Cookie expires in ${diff} day${diff >  1 || diff <= 0 ? 's' : ''}; expires=${expiraryDate.toString()}`;
      }
      
      #getDateDifference(a, b) {
        
        const _MS_PER_DAY = 1000 * 60 * 60 * 24;
        
        try {
          const utc1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate());
          const utc2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate());
          
          return Math.floor((utc2 - utc1) / _MS_PER_DAY);
        
        } catch(e) {
          throw e;      
        }
      }
      
      getItem(key) {
        
        let cookie = localStorage.getItem(this.name).trim();
        
        if(key == 'data') {
          return this.data;
        }
        
        return decodeURIComponent(cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" + encodeURIComponent(key).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1")) || null;
      }
      
      hasExpired() {
        return (Date.parse(this.expires) - Date.parse(new Date())) < 0;
      }
      
      hasItem(key) {
        let keys = this.getKeys();
    
        if (!key) { return false; }
    
        return keys.indexOf(key) > -1;
      }
      
      #isDate(s) {
        if(isNaN(s) && !isNaN(Date.parse(s)))
          return true;
        else return false;
      }
      
      async save(callback, merge) {
        if(window.fetch) {
          
          try {
            
            let data = null;
            let method = null;
        
            if(this.dataType  == 'array') {
              data = this.#convertArrayToJSON(this.data);
            } else if(this.dataType == 'json') {
              data = this.data;          
            }
            
            method = data.id != undefined || data.userId != null ? 'PUT' : 'POST';    
            
            let res = await fetch(this.url, {
              method: 'PUT',
              body: JSON.stringify(data),
              headers: {
                "Content-type": "application/json; charset=UTF-8"
              }
            });
    
            if(!res.ok) {
              callback({ error: 'An error occured, failed to send data to server.'});
              return false;
            }  
            
            data = await res.json();
            
            callback({ json: data });
          } catch(e) {
            throw e;
          }
        } else {
          console.log("Browser doesn't supports fetch.");
        }
      }
      
      #serializeArrayToString(arr) {
        const unsanitize = (x) => {
          const isInteger = (n) => { 
            let x = n * 1;
            return Number.isInteger(x);
          }
    
          const isFloat = (n) =>  {
            let parsed = Number.parseFloat(n);
            return (!Number.isNaN(parsed)) && (!Number.isInteger(parsed))
          }
    
          if(x == false || x == true) {
            x = x.toString();
          } else if(x instanceof Date) {
            x = x.toUTCString();
          } else if(isInteger(x)) {
            x = Number.parseInt(x, 10).toString();
          } else if(isFloat(value)) {
            x = Number.parseFloat(x).toString();
          } else if(typeof x == 'array') {
            console.log('hit');
            x = x.map(y => this.unsantize(y)).join('|');
          }
    
          return x;
        }
    
        const r = (obj, arr1, arr2) => {
    
          let keys = {...obj};
          let objKeys = Object.keys(keys);
          let counter = -1;
    
          objKeys.sort();
    
          for(const key in objKeys) {
            let arr = [...arr2]; 
            counter += 1;
            arr[0] = arr[0] + '-' + counter.toString();  
    
            if(/[A-Za-z]/.test(objKeys[key]) && Array.isArray(obj[objKeys[key]])) {
              arr.push(objKeys[key]);
              r(obj[objKeys[key]], arr1, arr);
            } else {
              arr.push(objKeys[key]);
              arr.push(obj[objKeys[key]]);
              arr1.push(arr)
            }
          }
        }
    
        let keys = {...arr}
        let arrKeys = Object.keys(keys);
        let str = '';
        let arr1 = [];
        let value = null;
        let level = -1;
    
        arrKeys.sort();
    
        for(const arrKey in arrKeys) {
          let key = arrKeys[arrKey];
          let obj = arr[key];
    
          level += 1;
    
          if(Array.isArray(obj)) {
    
            let nKeys  = {...obj};
            let isMixedArray = false;
    
            for(const nKey in nKeys) {
              if(/[A-Za-z]/.test(nKey)) {
                isMixedArray = true;
                break;
              }
            };
    
            if(isMixedArray) {
              r(arr[key], arr1, [level, key]);
            } else {
              value = obj;
              arr1.push([level.toString(), key, value])
            }
          } else {
            arr1.push([level.toString(), key, obj])
          }
        }
    
        str = arr1.map((item) => {
          let level = item[0];
          let root = null;
          let value = item[item.length - 1];
          let key = null;
    
          item.splice(0, 1);
          item.splice(-1, 1);
    
          if(item.length - 1 > 0) {
            root = item[0];
            item.splice(0, 1);
            item = item.join('][');
            key = root + '[' + item + ']';
          } else {
            key = item[0];
          }
    
          if(Array.isArray(value)) {
            value = value.map(x => unsanitize(x)).join('|');
          }
    
          return encodeURIComponent(key) + '=' + encodeURIComponent(value);
        }).join('&')
        return str;
      }
     
      #serializeStringToArray(value) {
        let arr = []; 
    
        const recursion = (arr, keys, i, value) => {
          let j = i;
          let arr2;
          let oKey = '';
    
          if(i < keys.length - 1) {
    
            if(Array.isArray(arr[keys[i]])) {
              arr2 = arr[keys[i]];
            } else {
              arr2 = arr[keys[i]] = []; 
            }
    
            i++;
    
            recursion(arr2, keys, i, value);
          }  else {
    
            arr[keys[i]] = this.sanitize(decodeURIComponent(value));
          }
        }
    
        this.sanitize = (value) => {
          let result = null;
    
          const convertStringToBoolean = (value) => value ? String(value).toLowerCase() === 'true' : false;
    
          const isInteger = (n) => { 
            let x = n * 1;
            return Number.isInteger(x);
          }
    
          const isFloat = (n) =>  {
              let parsed = Number.parseFloat(n);
              return (!Number.isNaN(parsed)) && (!Number.isInteger(parsed))
          }
    
          const sanitizeValue = (x) => {
    
            if(x.toLowerCase() == 'false' || x.toLowerCase() == 'true') {
              x = convertStringToBoolean(x);
            }else if(isInteger(x)) {
              x = Number.parseInt(x, 10);
            } else if(isFloat(value)) {
              x = Number.parseFloat(x);
            } else if(Date.parse(x)) {
              x = new Date(x);
            } 
    
            return x;
          }
    
          if(typeof value == 'string') {
            if(value.includes('|')) {
              result = value.split('|').map(x =>  {
                return sanitizeValue(x);
              });
            } else {
              result = sanitizeValue(value);
            }           
          }
    
          return result;
        }
    
        value.split('&').forEach(item => {
          let pair = item.split('=');
          let key = decodeURIComponent(pair[0]);
          let value = this.sanitize(decodeURIComponent(pair[1]));
    
          if(key.includes('[') && key.includes(']')) {
            let keys = key.replaceAll('[', ' ')
                          .replaceAll(']', ' ')
                          .replaceAll('  ', ' ')
                          .trim().split(' ');
    
            let i = 0;
            recursion(arr, keys, i, value);
          } else {
            arr[key] = value;  
          }
        });
    
        return arr;
      }  
     
      setExpiraryDate(value) {
        let expiraryDate;
        
        try {
          
          if(this.expires != null) {  
            if(value == 'now') {
              expiraryDate = new Date();
              this.#expiresNow = true;
            } else if(this.#isDate(value)) {   
              expiraryDate = new Date(value);              
            } else if(value.includes('+') && (value.includes('days') || value.includes('months') || value.includes('years'))) {
              expiraryDate = this.#createExpiraryDate(value);
            } else {
              throw new Error('Unable to parse expirary date.');  
            }
          } else {
            throw new Error('No expirary date created.');
          }
    
          this.expires = expiraryDate;
          this.setItem('expires', expiraryDate);
          this.#getStatus(this.expires);
          
        } catch(e) {
          throw e
        } 
        
        return this;
      };
      
      setItem(key, value) {
        let cookie,
            data,
            expiraryDate;
        
        try {
          cookie = localStorage.getItem(this.name);
          
          if(cookie == null) {
            throw new Error(`Cookie does not exist, cannot set ${key}.`)
          }
          
          if(this.hasItem(key) || key == 'data') {
            cookie = cookie.split('; ').map((item) => {
              if(key == 'data' && item.includes(this.name)) {
                if(this.#isJSONObject(value)) {
                  this.dataType = 'json';
                  this.data = value; 
                  value = encodeURIComponent(JSON.stringify(value));
                } else if(typeof value == 'array') {
                  this.dataType = 'array';
                  this.data = value; 
                  value = this.#serializeArrayToString(value);
                } else {
                  throw new Error('Cookie data is not a json object or array.')
                }
    
                item = `${this.name}=${value}`;
              }  
              
              if(key == 'expires' && item.includes('expires') && typeof value == 'date') {
                this.expires = value;
                item = `${key}=${value.toUTCString()}`;
              }
              
              if(key == 'path' && item.includes('path')) {
                item = `${key}=${value}`;
                this.path = value;
              }
    
              if(key == 'url' && item.includes('url')) {
                item = `${key}=${value}`;
                this.url = value;
              } 
    
              return item;
            }).join('; ');
          } else {
            cookie += `; ${key}=${value}`
          }
          
          this.value = cookie;
          
          localStorage.setItem(this.name, cookie);
          
          return true;
        } catch(e) {
          throw e;  
        }
        
        return false;
      }
      
      toObject() {
        let obj = {};
        
        for(const key in this) {
          obj[key] = this[key];  
        }
        
        return obj;
      }
    }
    
    class DeviceManager {
      constructor(...args) {
        let self = this;
        this.name = args[0];
        this.events = [];
        
        this.plugins = {
          add:function(plugin) {
            let obj = null;
            
            try {
              if (Object
                    .getPrototypeOf(plugin)
                    .constructor
                    .__proto__
                    .name == 'DevicePlugin') {
                this[plugin.name] = plugin;
                obj = this[plugin.name].init(self);
                
                if(obj.params != undefined) {
                  //self.params = Object.keys(obj.params);
                  
                  for(const param in obj.params) {
                    self[param] = obj.params[param];
                  }
                }
                
                if(plugin.methods != undefined) {
                  plugin.methods.map((method) => self[method] = this[plugin.name][method]);
                }
                
                if(plugin.setup) {
                  plugin.setup();
                  plugin.tasks().then(function(args) {
                    let tasks = args;
                    
                    tasks.forEach((task) => {
                      if(!self[task.param]) {
                        self[task.param] = {};
                      }
                      
                      self[task.param][task.name] = task.value;
                    });
                    
                    plugin.events.forEach((event) => {
                      self.events.push(event);
                      
                      if(event.exec) {
                        event.func({}, event.param, self);
                      } else {
                        window.addEventListener(event.name, (e) => {
                          event.func(e, event.param, self);
                        });
                      }
                    });
                    
                    
                    if(self['ondeviceorientation'] != undefined) {
                      window.addEventListener('deviceorientation', function(e) {
                        self.dispatch('deviceorientation', e, { browser: self.browser, os: self.os, device: self.device });  
                      });
                    }
                    
                    if(self['onorientationchange'] != undefined) {
                      window.addEventListener('orientationchange', function(e) {
                        self.dispatch('orientationchange', e, { browser: self.browser, os: self.os, device: self.device });  
                      });
                    }
                    
                    if(self['onscroll'] != undefined) {
                      window.addEventListener('scroll', function(e) {
                        self.dispatch('scroll', e, { browser: self.browser, os: self.os, device: self.device });  
                      });
                    }
                    
                    if(self['onresize'] != undefined) {
                      window.addEventListener('resize', function(e) {
                        self.dispatch('resize', e, { browser: self.browser, os: self.os, device: self.device });  
                      });
                    }
                  }).catch(function(e) {
                    throw e;
                  });
                }
              } else {
                throw new Error(`${plugin.constructor.name} is not a DevicePlugin`)
              }  
            } catch(e) {
              throw e;
            }
          },
          remove:function(plugin) {
            try{
              plugin = self.plugins[plugin];
              
              if(plugin != undefined) {
                
                if(plugin.params != undefined && Array.isArray(plugin.params)) {
                  plugin.params.forEach((param) => {
                    delete self[param];
                  });  
                }
                
                if(plugin.methods != undefined && Array.isArray(plugin.methods)) {
                  plugin.methods.forEach((method) => {
                    delete self[method];
                  });
                }
                
                if(plugin.events != undefined && Array.isArray(plugin.events)) {
                  plugin.events.forEach((event) => {
                    window.removeEventListener(event, function(e) {
                      console.log(`removing event: ${event}`); 
                    });
    
                    delete self['on' + event];
                  });
                }
                
                delete self.plugins[plugin.name];          
                return true;  
              }
              
              return false;
            } catch(e) {
              throw e;
            }
          }
        };
        
        this.getGeolocation = (self, callback) => {
          if (navigator.geolocation) {
            return navigator.geolocation.getCurrentPosition(function(position) {
              let el = document.querySelector('.geolocation');
              el.style.display = 'block';
              self.geolocation = {};
              
              el = document.querySelector('.geolocation__heading');
              el.innerText = position.coords.heading != null ? position.coords.heading : 'none';
              self.geolocation.heading = position.coords.heading;
              
              el = document.querySelector('.geolocation__altitudeAccuracy');
              el.innerText = position.coords.altitudeAccuracy != null ? position.coords.altitudeAccuracy : 'none';
              self.geolocation.altitudeAccuracy = position.coords.altitudeAccuracy;
              
              el = document.querySelector('.geolocation__accuracy');
              el.innerText = position.coords.accuracy != null ? position.coords.accuracy : 'none';
              self.geolocation.accuracy = position.coords.accuracy;
              
              el = document.querySelector('.geolocation__altitude');
               el.innerText = position.coords.altitude != null ? position.coords.altitude : 'stationary';
              self.geolocation.altitude = position.coords.altitude;
              
              el = document.querySelector('.geolocation__latitude');
              el.innerText = position.coords.latitude;
              self.geolocation.latitude = position.coords.latitude;
              
              el = document.querySelector('.geolocation__longitude');
              el.innerText = position.coords.longitude;
              self.geolocation.longitude = position.coords.longitude;
              
              el = document.querySelector('.geolocation__speed');
              el.innerText = position.coords.speed;
              self.geolocation.speed = position.coords.speed;
              
              if(callback) {
                callback(self.geolocation);
              }
            });
          } else { 
            console.log("Geolocation is not supported by this browser.");
          }
        };
        this.getGeolocation(this);
        
      }
      
      #createEvent(name, callback, param) {
        let self = this;
        
        if(this['on' + name] != undefined) {
          window.addEventListener(name, (e) => {
            self.dispatch(name, e, param);  
          });
        }
      }
      
      on(name, callback) {
        let self = this;
        var callbacks = this['on' + name];
        if (!callbacks) this['on' + name] = [callback];
        else callbacks.push(callback);
        
        this.#createEvent(name, callback, { browser: self.browser, os: self.os, device: self.device});
      }
    
      dispatch(name, event, prop) {
        var callbacks = this['on' + name];
        if (callbacks) callbacks.forEach(callback => callback.call(this, event, prop));
      }
    }
    
    class DevicePlugin {
      constructor(name) {
        this.name = name;   
      }
      
      init(self, name) {
        this.name = name;
      }
    }
    
    class BrowserPlugin extends DevicePlugin {
      #dataos;
      #databrowser;
      #devices;
      #header;
      
      constructor() {
        super('BrowserPlugin');
        this.#header = [
          navigator.platform, 
          navigator.userAgent, 
          navigator.appVersion, 
          navigator.vendor, 
          window.opera
        ];   
        this.#dataos = [
          { name:'Windows 10', value:'(Windows 10.0|Windows NT 10.0)', version:'NT' },
          { name:'Windows 8.1', value:'(Windows 8.1|Windows NT 6.3)', version:'NT' },
          { name:'Windows 8', value:'(Windows 8|Windows NT 6.2)', version:'NT' },
          { name:'Windows 7', vallue:'(Windows 7|Windows NT 6.1)', version:'6.1' },
          { name:'Windows Vista', value:'Windows NT 6.0', version:'NT' },
          { name:'Windows Server 2003', value:'Windows NT 5.2', version:'NT' },
          { name:'Windows XP', value:'(Windows NT 5.1|Windows XP)', version:'NT' },
          { name:'Windows 2000', value:'(Windows NT 5.0|Windows 2000)', version:'NT' },
          { name:'Windows ME', value: '(Win 9x 4.90|Windows ME)', version:'9x' },
          { name:'Windows 98', value:'(Windows 98|Win98)', version:'Windows'},
          { name:'Windows 95', value:'(Windows 95|Win95|Windows_95)', version:'Windows' },
          { name:'Windows NT 4.0', value:'(Windows NT 4.0|WinNT4.0|WinNT|Windows NT)', version: 'NT'},
          { name:'Windows CE', value:'Windows CE', version: 'CE' },
          { name:'Windows 3.11', value:'Win16', version: 'Win16' },
          { name:'Android', value:'Android', version:'Android' },
          { name:'Open BSD', value:'OpenBSD', version:'OpenBSD' },
          { name:'Sun OS', value:'SunOS', version:'SunOS' },
          { name:'Chrome OS', value:'CrOS', version: 'OS' },
          { name:'Linux', value:'Linux|X11(?!.*CrOS)', version:'X11'},
            // {s:'iOS', r:/(iPhone|iPad|iPod)/},
          { name:'Mac OS X', value:'Mac OS X', version:'Mac OS'},
          { name:'Mac OS', value:'(MacPPC|MacIntel|Mac_PowerPC|Macintosh)', version:'(MacPPC|MacIntel|Mac_PowerPC|Macintosh)'},
          { name:'QNX', value:'QNX', version:'QNX' },
          { name:'UNIX', value:'UNIX', version:'UNIX' },
          { name:'BeOS', value:'BeOS', version:'BeOS' },
          { name:'OS/2', value:'OS\/2', version:'OS/2' },
          { name:'Search Bot', value:'(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)', version:'(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)' },
          { name: 'Windows Phone', value: 'Windows Phone', version: 'Windows Phone' },
          { name: 'Windows', value: 'Win', version: 'NT' },
          { name: 'iPhone', value: 'iPhone', version: 'OS' },
          { name: 'iPad', value: 'iPad', version: 'OS' },
          { name: 'Kindle', value: 'Silk', version: 'Silk' },
          { name: 'Android', value: 'Android', version: 'Android' },
          { name: 'PlayBook', value: 'PlayBook', version: 'OS' },
          { name: 'BlackBerry', value: 'BlackBerry', version: '/' },
          { name: 'Macintosh', value: 'Mac', version: 'OS X' },
          { name: 'Linux', value: 'Linux', version: 'rv' },
          { name: 'Palm', value: 'Palm', version: 'PalmOS' }
        ];   
        this.#databrowser = [
          { name: 'Edge', value: 'Edg', version: 'Edg' },
          { name: 'Chrome', value: 'Chrome', version: 'Chrome' },
          { name: 'Firefox', value: 'Firefox', version: 'Firefox' },
          { name: 'Safari', value: 'Safari', version: 'Version' },
          { name: 'Internet Explorer', value: 'MSIE', version: 'MSIE' },
          { name: 'Opera', value: 'Opera', version: 'Opera' },
          { name: 'BlackBerry', value: 'CLDC', version: 'CLDC' },
          { name: 'Mozilla', value: 'Mozilla', version: 'Mozilla' }
        ];
        this.#devices = [
          { name: 'iPad', regexp: /ipad/i },
          { name: 'iPhone', regexp: /iphone/i },
          { name: 'iPod', regexp: /ipod/i },
          { name: 'iDevice', regexp: /ipad|iphone|ipod/i },
          { name: 'Android', regexp: /android/i },
          { name: 'Blackberry', regexp: /blackberry/i },
          { name: 'WebOS', regexp: /webos/i },
          { name: 'Windows Phone', regexp: /windows phone/i }
        ];
        this.params = [
                        'browser', 
                        'os', 
                        'device'
                      ];
        this.methods = ['update'];
        this.events = []
        this.taskList = [];
        this.resizeId = null;
      }
      
      #matchItem(string, data) {
        var i = 0,
            j = 0,
            html = '',
            regex,
            regexv,
            match,
            matches,
            version;
    
        for (i = 0; i < data.length; i += 1) {
          regex = new RegExp(data[i].value, 'i');
          match = regex.test(string);
          if (match) {
            regexv = new RegExp(data[i].version + '[- /:;]([\\d._]+)', 'i');
            matches = string.match(regexv);
            version = '';
            if (matches) { if (matches[1]) { matches = matches[1]; } }
            if (matches) {
              matches = matches.split(/[._]+/);
              for (j = 0; j < matches.length; j += 1) {
                if (j === 0) {
                  version += matches[j] + '.';
                } else {
                  version += matches[j];
                }
              }
            } else {
              version = '0';
            }
            return {
              name: data[i].name,
              version: parseFloat(version)
            };
          }
        }
        return { name: 'unknown', version: 0 };
      }
      
      update(props) {
        this.browser = props.browser;
        this.device = props.device;
        this.os = props.os;
      }
      
      init(self, name) {
        var agent = this.#header.join(' '),
            os = this.#matchItem(agent, this.#dataos),
            browser = this.#matchItem(agent, this.#databrowser);
            browser.navigator = navigator;
            browser.info = null;
        
        browser.cookieEnabled = navigator.cookieEnabled ? true : false;
        browser.width = window.innerWidth || document.body.clientWidth;
        browser.height = window.innerHeight || document.body.clientHeight;
        browser.cookies = {
          data: function() {
            let result = localStorage.getItem('cookies');
            if(typeof result == 'string') {
              return JSON.parse(result);
            } 
            
            return { items: [] };
          },
          add: function(cookie) {
            try {
              let data = this.data();
              
              if(cookie.constructor.name == 'DeviceCookie') {
                data.items[cookie.name] = cookie.toObject();
                localStorage.setItem('cookies', JSON.stringify(data));
                return true;
              } else {
                throw new Error(`${cookie.constructor.name} is not a DeviceCookie`);
              }
              
              return false;
            } catch(e) {
              throw e;
            }
          },
          remove: function(name) {
            if(browser.cookies.data.has(name)) {
              browser.cookies.data.delete(name);
              localStorage.setItem('cookies', browser.cookies.data);
              return true;
            }
    
            return false;
          },
          each: function(callback) {
            
            try {
              let data = this.data();
              let keys = Object.keys(data.items);
              
              keys.forEach((key, index) => {
                
                callback.call(self, new DeviceCookie(data.items[key]), index);
              });
            } catch(e) {
              throw e;
            }
          }
        };
        
        let device = {
          'isAndroid': function() {
            return navigator.userAgent.match(/Android/i);
          },
          'isDatalogic': function() {
            return navigator.userAgent.match(/DL-AXIS/i);
          },
          'isBluebird': function() {
            return navigator.userAgent.match(/EF500/i);
          },
          'isHoneywell': function() {
            return navigator.userAgent.match(/CT50/i);
          },
          'isZebra': function() {
            return navigator.userAgent.match(/TC70|TC55/i);
          },
          'isBlackBerry': function() {
            return navigator.userAgent.match(/BlackBerry/i);
          },
          'isiPod': function() {
            return navigator.userAgent.match(/iPod/i);
          },
          'isiPhone': function() {
            return navigator.userAgent.match(/iPhone/i);
          },
          'isiPad': function() {
            return navigator.userAgent.match(/iPad/i);
          },
          'isWindowsPhone': function() {
            return navigator.userAgent.match(/IEMobile/i) || navigator.userAgent.match(/Windows Phone/i);
          },
          'isNexus7': function() {
            return navigator.userAgent.match(/Nexus 7/i);
          },
          'isNexus10': function() {
            return navigator.userAgent.match(/Nexus 10/i);
          },
          'isWebOS': function() {
            return navigator.userAgent.match(/webOS/i);
          },
          'isAmazonKindle': function() {
            return navigator.userAgent.match(/KFAPWI/i);
          },
          'any': function() {
            return (this.isDatalogic() || 
                    this.isBluebird() || 
                    this.isHoneywell() || 
                    this.isZebra() || 
                    this.isBlackBerry() || 
                    this.isAndroid() || 
                    this.isiPod() || 
                    this.isiPhone() || 
                    this.isiPad() || 
                    this.isWindowsPhone() ||
                    this.isNexus7() ||
                    this.isNexus10() ||
                    this.isWebOS() ||
                    this.isAmazonKindle());
            },
          'isMobile': function() {
            return this.any();
          }
        };
        
        if(/(up.browser|up.link|mmp|symbian|smartphone|midp|wap|phone|android|iemobile|w3c|acs\-|alav|alca|amoi|audi|avan|benq|bird|blac|blaz|brew|cell|cldc|cmd\-|dang|doco|eric|hipt|inno|ipaq|java|jigs|kddi|keji|leno|lg\-c|lg\-d|lg\-g|lge\-|maui|maxo|midp|mits|mmef|mobi|mot\-|moto|mwbp|nec\-|newt|noki|palm|pana|pant|phil|play|port|prox|qwap|sage|sams|sany|sch\-|sec\-|send|seri|sgh\-|shar|sie\-|siem|smal|smar|sony|sph\-|symb|t\-mo|teli|tim\-|tosh|tsm\-|upg1|upsi|vk\-v|voda|wap\-|wapa|wapi|wapp|wapr|webc|winw|winw|xda|xda\-) /i.test(navigator.userAgent))
        {
          if(/(tablet|ipad|playbook)|(android(?!.*(mobi|opera mini)))/i.test(navigator.userAgent)) 
          {
            device.type = 'Tablet';
          }
          else
          {
            device.type = 'Mobile';
          }
        }
        else if(/(tablet|ipad|playbook)|(android(?!.*(mobi|opera mini)))/i.test(navigator.userAgent)) 
        {
          device.type = 'Tablet';
        }
        else
        {
          device.type = 'Desktop';
        }
        
        device.name = null;
        device.width = screen.width;
        device.height = screen.height;
    
        for (var id in this.#devices) {
          var ds = this.#devices[id];
          if (ds.regexp.test(navigator.userAgent)) {
            device.name = ds.name;
            break;
          }
        }
        
        if(window.orientation && typeof window.orientation === 'number') {
          if(Math.abs(window.orientation) == 90) {
            device.orientation = 'Landscape';
          } else {
            device.orientation = 'Portrait';
          }
        } else {
          device.orientation = null; 
        }
           
        os.platform = navigator.platform;
        os.language = navigator.language;
        
        browser.scroll = (el = window) => {
          
          return {
            left: el.scrollX,
            top: el.scrollY
          };
        };
    
        browser.userAgent = this.#header.join(' ');
        
        this.browser = browser;
        this.os = os;
        this.device = device;
        
        return { params: { device: device, os: os, browser: browser } };
      }
      
      #load(event, param, self) {
        console.log('Plugin Load Event')
      }
      
      #deviceorientation(event, props, self) {
        if(self.device.type != 'Desktop') {
          switch(event.currentTarget.orientation) {  
            case -90: case 90:
              self.device.orientation = 'Landscape';
              break; 
            default:
              self.device.orientation = 'Portrait';    
              break; 
          } 
        } else {
          self.device.orientation = 'default';
        }
        
        self.browser.width = window.innerWidth || document.body.clientWidth;
        self.browser.height = window.innerHeight || document.body.clientHeight;  
        self.update(props);
      }
      
      #orientationchange(event, props, self) {
    
        switch(event.currentTarget.orientation) {  
          case -90: 
          case 90:
            self.device.orientation = 'Landscape';
            self.device.width = window.screen.width < window.outerWidth ?
              window.screen.width : window.outerWidth;
            self.device.height = window.screen.height < window.outerHeight ?
              window.screen.height : window.outerHeight;
            self.browser.width = window.innerWidth || document.body.clientWidth;
            self.browser.height = window.innerHeight || document.body.clientHeight;  
    
            break; 
          default:
            self.device.orientation = 'Portrait';  
    
            self.device.width = window.screen.width < window.outerWidth ?
              window.screen.width : window.outerWidth;
            self.device.height = window.screen.height < window.outerHeight ?
              window.screen.height : window.outerHeight;
            self.browser.width = window.innerHeight || document.body.clientHeight;  
            self.browser.height = window.innerWidth || document.body.clientWidth;
    
            break; 
        }
    
        props = {device: self.device, 'os': self.os, 'browser': self.browser};
        self.update(props);
      }
      
      #scroll(event, props, self) {
        props = { browser:self.browser, os:self.os, device:self.device}
        self.update(props);
      }
      
      #resize(event, props, self) {
    
        clearTimeout(this.resizeId);
    
        this.resizeId = setTimeout(function() {
          self.browser.width = window.innerWidth || document.body.clientWidth;
          self.browser.height = window.innerHeight || document.body.clientHeight;  
          props = { device: self.device, 'os': self.os, 'browser': self.browser };
          self.update(props);
        }, 500);
      }
      
      setup() {
        let ajax = async (url) => {
          let res = await fetch(url, {
            method: 'GET',
            headers: {
              "Content-type": "application/json; charset=UTF-8"
            }
          });
          
          if(!res.ok) {
            return Promise.reject(res);
          } else {
            let data = await res.json();
            return Promise.resolve(data);
          }
        };
        
        const ipinfo = getIpinfo => new Promise((resolve, reject) => {
          ajax('https://ipinfo.io?token=1c099bf62c3df9')
            .then(function(res) {
            return resolve({ name: 'ipinfo', value: res, param: 'browser' });
          }).catch(function(res) {
            return reject(res);
          });                
        }); 
        
        /* TASKS */
        this.taskList.push({ name: 'ipinfo', func: ipinfo });
        
        /* EVENTS */
        this.events.push({ exec: true, func: this.#load, name: 'load', param: { browser: this.browser, os: this.os, device: this.device }});
        event = this.#deviceorientation;
        this.events.push({ exec: false, func: this.#deviceorientation, name: 'deviceorientation', param: { browser: this.browser, os: this.os, device: this.device }});
        this.events.push({ exec: false, func: this.#orientationchange, name: 'orientationchange', param: { browser: this.browser, os: this.os, device: this.device }});
        this.events.push({ exec: false, func: this.#scroll, name: 'scroll', param: { browser: this.browser, os: this.os, device: this.device }});
        this.events.push({ exec: false, func: this.#resize, name: 'resize', param: { browser: this.browser, os: this.os, device: this.device }});
      }
      
      async tasks() {
        return await Promise.all(this.taskList.length > -1 ? this.taskList.map(async (task) => await task.func()) : function() { return true });
        };
      }
    
    class HistoryPlugin extends DevicePlugin {
      constructor() {
        super('HistoryPlugin');    
      }
      
      init(self) {
        return { params: {
          history: {
            data: function() {
              //localStorage.removeItem('history');       
              let result = localStorage.getItem('history');
              if(localStorage.getItem('history') != null) {
                result = JSON.parse(result);
              } else {
                result = { items: [] };
                localStorage.setItem('history', JSON.stringify(result));
              }
              
              return result;
            },
            add: function(history) {
              try {
                let data = this.data();
                let item = data.items.find((item) => item.id == history.id);
                
                if(typeof history != 'object') {
                  throw new Error(`${history.constructor.name} is not an object.`);
                }
                
                if(item == undefined) {
                  data.items.push(history);
                  localStorage.setItem('history', JSON.stringify(data));
                  return true;
                } 
                
                return false;
              } catch(e) {
                throw e;
              }
            },
            remove: function(id) {
              try {
                let data = this.data();
                if(id != undefined || id != null && Number.isInteger(id)) {
                  data.items = data.items.filter((item) => item.id != id);
                  localStorage.setItem('history', JSON.stringify(data));  
                  return true;  
                } else {
                  throw new Error(`Paramater "id" is not an integer.`);
                }
              
                return false;  
              } catch(e) {
                throw e
              }
            },
            set: function(id, obj) {
              try {
                let data = this.data();
                
                if(!Number.isInteger(id)) {
                  throw new Error(`Paramater "id" is not an integer.`);
                } 
                
                if(typeof obj != 'object') {
                  throw new Error(`Paramater "obj" is not an object.`);
                }
                
                data.items = data.items.map((data) => {
                  if(data.id == id) {
                    data = Object.assign(data, obj);
                  }
    
                  return data;
                });
                
                if(data) {
                  localStorage.setItem('history', JSON.stringify(data));
                  return true;  
                }
                
                return false;
              } catch(e) {
                throw e
              }
            },
            get: function(id) {
              return this.data().items.find((item) => item.id == id);
            },
            each: function(callback) {
              try {
                let data = this.data();
                if(typeof callback == 'function') {
                  data.items.forEach((data, index) => {
                    callback.call(self, data, index);
                  });
                } else {
                  throw new Error('The callback is not a function.')
                } 
              } catch(e) {
                throw e;
              }
            }
          }
        }};  
      }
    }
    
    class FavoritesPlugin extends DevicePlugin {
      constructor() {
        super('FavoritesPlugin');    
      }
      
      init(self) {
        return { params: {
          favorites: {
            data: function() {
              let result = localStorage.getItem('favorites');
              //localStorage.removeItem('favorites');
              
              if(result != null) {
                result = JSON.parse(result);
              } else {
                result = { items: [] };
                localStorage.setItem('favorites', JSON.stringify(result));
              }
              
              return result;
            },
            add: function(favorite) {
              try {
                let data = this.data();
                let item = Array.from(data.items).find((item) => item.id == favorite.id);
                
                if(typeof favorite != 'object') {
                  throw new Error(`Paramater "data" is not an Object.`);
                }
                
                if(item == undefined) {
                  data.items.push(favorite);
                  localStorage.setItem('favorites', JSON.stringify(data));
                  return true;
                } 
                
                return false;
              } catch(e) {
                throw e;
              }
            },
            remove: function(id) {
              try {
                let data = this.data();
                
                if(!Number.isInteger(id)) {
                  throw new Error(`Paramter "id" is not an integer.`)
                }
                
                if(id) {
                  data.items = data.items.filter((item) => item.id != id);
                  localStorage.setItem('favorites', JSON.stringify(data));  
                  return true;  
                } 
              
                return false;  
              } catch(e) {
                throw e
              }
            },
            set: function(id, obj) {
              try {
                let data = this.data()
                
                if(!Number.isInteger(id)) {
                  throw new Error(`Paramater "id" is not an integer.`);
                }
                
                if(typeof obj != 'object') {
                  throw new Error(`Paramater "obj" is not an object.`);
                }
                
                data.items = data.items.map((data) => {
                  if(data.id == id) {
                    data = Object.assign(data, obj);
                  }
    
                  return data;
                });
                
                if(data) {
                  localStorage.setItem('favorites', JSON.stringify(data));
                  return true;  
                } 
                
                return false;
              } catch(e) {
                throw e
              }
            },
            get: function(id) {
              return Array.from(this.data().items).find((item) => item.id == id);
            },
            each: function(callback) {
              try {
                let data = this.data();
                
                if(typeof callback == 'function') {
                  data.items.forEach((data, index) => {
                    callback.call(self, data, index);
                  });
                } else {
                  throw new Error('The callback is not a function.')
                }
              } catch(e) {
                throw e;
              }          
            }
          }
        }};  
      }
    }
    
    let data = [];
    data['user_name'] = 'John.D.';
    data['first_name'] = 'John';
    data['last_name'] = 'Doe';
    data['full_name'] = 'John Doe';
    data['email'] = 'john@doe.com';
    data['gender'] = 'Male';
    data['address'] = [];
    data['address']['id'] = 100;
    data['address']['name'] = 'Kirk Dixon-Mciver';
    data['address']['thoroughfare'] = '17 Liverpool Street';
    data['address']['premise'] = null;;
    data['address']['sub_premise'] = null;
    data['address']['locality'] = 'Wellington';
    data['address']['administrative_area'] = 'Masterton';
    data['address']['postal_code'] = 5810;
    data['address']['country'] = 'New Zealand';
    data['settings'] = [];
    data['settings']['cache'] = 60;
    data['settings']['theme'] = [];
    data['settings']['theme']['color'] = 'theme__color--blue';
    data['settings']['theme']['style'] = 'theme__style--dark';
    data['settings']['theme']['border'] = 'theme__border--rounded';
    data['settings']['theme']['invert'] = false;
    data['settings']['theme']['background'] = [];
    data['settings']['theme']['background']['mobile'] = 'mobile.jpg';
    data['settings']['theme']['background']['tablet'] = 'tablet.jpg';
    data['settings']['theme']['background']['desktop'] = 'desktop.jpg';
    
    // let data2 = {
    //   'first_name': 'John',
    //   'last_name': 'Doe',
    //   'full_name': 'John Doe',
    //   'gender': 'Male',
    //   'email': 'johndoe@outlook.com',
    //   'address': {
    //     'id': 100,
    //     'name': 'Kirk Dixon-Mciver',
    //     'thoroughfare': '17 Liverpool Street',
    //     'premise': null,
    //     'sub_premise': null,
    //     'locality': 'Wellington',
    //     'administrative_area': 'Masterton',
    //     'postal_code': 5810,
    //     'country': 'New Zealand'
    //   },
    //   'setting': {
    //     'cache': 60,
    //     'theme': {
    //       'color': 'theme__color--blue',
    //       'border': 'theme__border--rounded',
    //       'style': 'theme__style--dark',
    //       'invert': false,
    //       'background': {
    //         'image': {
    //           'mobile': 'image.jpg',
    //           'tablet': 'image.jpg',
    //           'desktop': 'image.jpg',
    //         }
    //       }
    //     }
    //   }
    // }
    
    
    
    //console.log(cookie);
    
    
    //console.log(cookie2);
    
    
    // let cookie2 = sys.cookie('mysite');
    // cookie2.setExpiraryDate('+3 days');
    // console.log('keys', cookie2.getKeys());
    // console.log('has url', cookie2.hasItem('url'));
    // console.log('set data', cookie2.setItem('data', data2));
    // console.log('getItem', cookie2.getItem('data'));
    // console.log('hasExpired', cookie2.hasExpired());
    
    // cookie2.save(function(res) {
    //   console.table(res.json);
    // });
    // if(cookie2.destroy()) {
    //   cookie2 = undefined;
    // };
    //console.log('cookie2=', cookie2);
    
    function initMap() {}
    
    const componentForm = {
      street_number: "short_name",
      route: "long_name",
      locality: "long_name",
      administrative_area_level_1: "short_name",
      country: "long_name",
      postal_code: "short_name",
    };
    
    function fillInAddress() {
      // Get the place details from the autocomplete object.
      const place = autocomplete.getPlace();
    
      for (const component in componentForm) {
        document.getElementById(component).value = "";
        document.getElementById(component).disabled = false;
      }
    
      // Get each component of the address from the place details,
      // and then fill-in the corresponding field on the form.
      for (const component of place.address_components) {
        const addressType = component.types[0];
    
        if (componentForm[addressType]) {
          const val = component[componentForm[addressType]];
          document.getElementById(addressType).innerText = val;
        }
      }
    }
    
    
    function init() {
     // Create the autocomplete object, restricting the search predictions to
      // geographical location types.
      autocomplete = new google.maps.places.Autocomplete(
        document.getElementById("search__address"),
        { types: ["geocode"] }
      );
      // Avoid paying for data that you don't need by restricting the set of
      // place fields that are returned to just the address components.
      autocomplete.setFields(["address_component"]);
      // When the user selects an address from the drop-down, populate the
      // address fields in the form.
      autocomplete.addListener("place_changed", fillInAddress);
    }
    
    
    let scrollTimeout = null;
    let device = sys.device('sys.device');
    
    device.plugins.add(sys.plugin('browser'));
    device.plugins.add(sys.plugin('history'));
    device.plugins.add(sys.plugin('favorites'));
    //device.browser.cookie.remove(userCookie.name);
    
    device.on('load', function(event, props) {
      let id =1;
      let views =100;
      
      let cookie = sys.cookie('user', 'now', '/', document.domain, {
        data: {
          title: 'Mr',
          first_name: 'John',
          last_name: 'Doe',
          gender: 'Male',
          body: 'bar',
          userId: 1
        },
        dataType: 'json',
        url: 'https://jsonplaceholder.typicode.com/posts/1'
      });
      
      
      this.browser.cookies.add(cookie);
      
        document.getElementById('first_name').innerText = cookie.data.first_name;
       document.getElementById('last_name').innerText = cookie.data.last_name;
       document.getElementById('full_name').innerText = cookie.data.first_name + ' ' + cookie.data.last_name;
       document.getElementById('gender').innerText = cookie.data.gender;
      
      
      this.browser.cookies.each(function(cookie, index) {
        let el = document.createElement('li');
        el.innerText = cookie.name;
        document.querySelector('.cookies__list').appendChild(el);
      });
      
      //this.browser.cookies.remove(cookie.name)
      //console.log(this.browser.cookies.data);
      
      this.history.add({ id: id,
        created_at: new Date(), 
        title: document.querySelector('head title').innerText, 
        url: window.location.href,
        views: typeof views == 'string' ? parseInt(views, 10) + 1 : 1 });
    
      this.history.each(function(data, index) {
        let el = document.createElement('li');
        el.innerText = data.title;
        document.querySelector('.history__list').appendChild(el);
      });
      
      this.favorites.add({ id: id, 
        created_at: new Date(), 
        title: document.querySelector('head title').innerText, 
        url: window.location.href });
      
      this.favorites.each(function(data, index) {
        let el = document.createElement('li');
        el.innerText = data.title;
        document.querySelector('.favorites__list').appendChild(el);
      });
      
      document.querySelector('#useragent').innerText = props.browser.userAgent;
      
      this.getGeolocation(this, function(geolocation) {
        const location = { lat: geolocation.latitude, lng: geolocation.longitude };
        const map = new google.maps.Map(document.getElementById("map"), {
          zoom: 16,
          center: location,
        });
        
        const marker = new google.maps.Marker({
          position: location,
          map: map,
        });
      });
      
      init();
      
      console.log('Load Event');
      display(props, this);
      //console.log('favorites', this.favorites.data);
      //console.log('history', this.history.data);
    });
    
    device.on('orientationchange', function(event, props) {
      //console.log('ORIENTATIONCHANGE', props);
      //console.log('ORIENTATION', props.device.orientation);
      display(props, this);
      console.log('Orientation Change');
    });
    
    device.on('deviceorientation', function(event, props) {
      display(props, this);
      console.log('Device Orientation')
    });
    
    device.on('resize', function(event, props) {
      console.log('Resize Event');
      display(props, this);
    });
    
    device.on('scroll', function(event, props) {
      
      let showProps = function(self) {
        console.log('Scroll Event');
        display(props, self);
      }
      
      if (scrollTimeout) clearTimeout(scrollTimeout);
        scrollTimeout = setTimeout(function(){showProps(this)},500);  
    });
    
    device.on('beforeunload', function(event, props) {
      console.log('Before Unload Event');
    });
    
    device.on('unload', function(event, props) {
      console.log('Unload Event');  
    });
    
    device.on('ready', function(event, props) {
      console.log('Loaded Event');  
    });
    
    device.on('hashchange', function(event, props) {
      console.log('Hash Change Event');  
    });
    
    device.on('error', function(event, props) {
      console.log('Error Event');
    });
    
    device.on('online', function(event, props) {
      console.log('Online Event');
    });
    
    device.on('offline', function(event, props) {
      console.log('Offline Event');
    });
    
    device.on('beforeprint', function(event, props) {
      console.log('Before Print Event');
    });
    
    device.on('afterprint', function(event, props) {
      console.log('After Print Event');
    });
    
    //console.log(device.plugins.remove('BrowserPlugin'))
    
    
    function display(props, self) {
      let el = document.querySelector('.browser__name');
      el.innerText = props.browser.name;
      el = document.querySelector('.browser__cookieEnabled');
      el.innerText = props.browser.cookieEnabled;
      el = document.querySelector('.browser__width');
      el.innerText = props.browser.width;
      el = document.querySelector('.browser__height');
      el.innerText = props.browser.height;
      el = document.querySelector('.browser__version');
      el.innerText = props.browser.version;
      el = document.querySelector('.browser__scrollTop');
      el.innerText = props.browser.scroll().top;
      el = document.querySelector('.browser__scrollLeft');
      el.innerText = props.browser.scroll().left;
      
      el = document.querySelector('.device__name');
      el.innerText = props.device.name != null ? props.device.name : 'none';
      el = document.querySelector('.device__orientation');
      el.innerText = props.device.orientation;
      el = document.querySelector('.device__type');
      el.innerText = props.device.type;
      el = document.querySelector('.device__width');
      el.innerText = props.device.width;
      el = document.querySelector('.device__height');
      el.innerText = props.device.height;
      
      el = document.querySelector('.os__name');
      el.innerText = props.os.name;
      el = document.querySelector('.os__language');
      el.innerText = props.os.language;
      el = document.querySelector('.os__platform');
      el.innerText = props.os.platform;
      el = document.querySelector('.os__version');
      el.innerText = props.os.version;
      
      if(props.browser.ipinfo) {
        document.querySelector('.ipinfo').style.display = 'block';
        el = document.querySelector('.browser__ipinfo__country');
        el.innerText = props.browser.ipinfo.country;
        el = document.querySelector('.browser__ipinfo__city');
        el.innerText = props.browser.ipinfo.city;
        el = document.querySelector('.browser__ipinfo__hostname');
        el.innerText = props.browser.ipinfo.hostname;
        el = document.querySelector('.browser__ipinfo__ip');
        el.innerText = props.browser.ipinfo.ip;
        el = document.querySelector('.browser__ipinfo__loc');
        el.innerText = props.browser.ipinfo.loc;
        el = document.querySelector('.browser__ipinfo__org');
        el.innerText = props.browser.ipinfo.org;
        el = document.querySelector('.browser__ipinfo__postal');
        el.innerText = props.browser.ipinfo.postal;
        el = document.querySelector('.browser__ipinfo__region');
        el.innerText = props.browser.ipinfo.region;
        el = document.querySelector('.browser__ipinfo__timezone');
        el.innerText = props.browser.ipinfo.timezone;
      }
      
      // setTimeout((function(el, self) {
      //   if(self.geolocation) {
      //     el = document.querySelector('.geolocation');
      //     el.style.display = 'block';
      //     el = document.querySelector('.geolocation__heading');
      //     el.innerText = self.geolocation.heading;
      //     el = document.querySelector('.geolocation__altitudeAccuracy');
      //     el.innerText = self.geolocation.altitudeAccuracy;
      //     el = document.querySelector('.geolocation__accuracy');
      //     el.innerText = self.geolocation.accuracy;
      //     el = document.querySelector('.geolocation__altitude');
      //     el.innerText = self.geolocation.altitude;
      //     el = document.querySelector('.geolocation__latitude');
      //     el.innerText = self.geolocation.latitude;
      //     el = document.querySelector('.geolocation__longitude');
      //     el.innerText = self.geolocation.longitude;
      //     el = document.querySelector('.geolocation__speed');
      //     el.innerText = self.geolocation.speed;
      //   }
      // })(el, self), 2000);
    }
    
    function wait(func, timeout) {
      return new Promise(resolve => {
        func();
        setTimeout(resolve, timeout);
      });
    };
    
    console.log(device);
    </script>
    
    <script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyBizsVlfst5DHbR_p-gEmKzlUoVuVE_fjs&libraries=places&callback=initMap"></script></main>