commit 4b42391f4e2ec144f126eb6dadcde8123f9ec631 Author: Sebastian Kippe Date: Thu Jan 2 13:51:50 2020 -0500 Hello world diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..99ed0d4 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +notes.txt diff --git a/index.html b/index.html new file mode 100644 index 0000000..859b1bd --- /dev/null +++ b/index.html @@ -0,0 +1,107 @@ + + + + + + RS Location + + + + + + +

RS Location

+

+ This page lets you geocode your current location (centered on the nearest + city, or entered manually), and upload both a GeoJSON document as well as + map image to your remote storage. +

+ + + +
+
+

Settings

+ +

Geocoding

+

+ +

+

Maps

+

+ +

+

+ +

+
+ +
+
+

New location

+

+ +

+
+

+ + +

+
+
+ + + +
+ +
+ + +
+
+ + diff --git a/main.mjs b/main.mjs new file mode 100644 index 0000000..3a63ee7 --- /dev/null +++ b/main.mjs @@ -0,0 +1,238 @@ +import ApiKeys from './modules/rs-module-api-keys.mjs'; +import Geocoder from './modules/geocode.mjs'; +import initializeSettings from './modules/settings.mjs'; +import initializeProfileUpdates from './modules/profile.mjs'; +import { showElement, hideElement, renderImage } from './modules/dom-helpers.mjs'; + +const remoteStorage = new RemoteStorage({ modules: [ApiKeys] }); +remoteStorage.access.claim('profile', 'rw'); +remoteStorage.access.claim('api-keys', 'rw'); +remoteStorage.caching.enable('/profile/'); +remoteStorage.caching.enable('/public/profile/'); + +const widget = new Widget(remoteStorage, { + modalBackdrop: true, + skipInitial: true +}); +widget.attach('rs-widget-container'); + +document.querySelector('.rs-account .disconnect').addEventListener('click', e => { + remoteStorage.disconnect(); +}); + +let data = { + currentUserLocation: { + coords: null, + openCageResult: null + }, + currentCity: { + coords: null, + openCageResult: null, + geoJSON: null, + imageBlob: null, + imageObjectURL: null + } +}; + +data.currentUserLocation = new Proxy(data.currentUserLocation, { + set (obj, prop, value) { + obj[prop] = value; + + switch(prop) { + case 'coords': + renderCoordinates(); + break; + case 'openCageResult': + renderFormattedResult(); + break; + } + return true; + } +}); + +data.currentCity = new Proxy(data.currentCity, { + set (obj, prop, value) { + obj[prop] = value; + + switch(prop) { + case 'coords': + fetchMapImage(); + break; + case 'openCageResult': + renderFormattedCityResult(); + createGeoJSON(); + break; + case 'geoJSON': + break; + case 'imageBlob': + break; + case 'imageObjectURL': + renderMapImage(); + break; + } + return true; + } +}); + +remoteStorage.on('not-connected', () => { + showElement('.view.disconnected'); +}); + +remoteStorage.on('connected', () => { + hideElement('.view.disconnected'); + showElement('.view.connected'); + + document.querySelector('.rs-account .user-address').innerHTML = remoteStorage.remote.userAddress; + + initializeSettings(remoteStorage); + initializeProfileUpdates(remoteStorage, data); +}); + +remoteStorage.on('disconnected', () => { + hideElement('.view.connected'); + showElement('.view.disconnected'); +}); + +const geocoder = new Geocoder(remoteStorage); + +const mapBoxStyle = 'v1/mapbox/streets-v10' +const imageSize = '260x100@2x' + +document.querySelector('button.get-coordinates').addEventListener('click', () => { + console.debug('Getting current/GPS location...'); + navigator.geolocation.getCurrentPosition(async position => { + console.debug('Location data:', position); + data.currentUserLocation.coords = { + lat: position.coords.latitude.toFixed(4), + lng: position.coords.longitude.toFixed(4) + }; + await geocodeUserLocation(); + geocodeNearestCity(); + }); +}) + +document.querySelector('form.get-location').addEventListener('submit', e => { + e.preventDefault(); + console.debug('Geocoding supplied location...'); + geocodeLocationInput().catch(err => window.alert(err)); +}) + +async function geocodeUserLocation () { + console.debug('Geocoding coordinates...'); + const { lat, lng } = data.currentUserLocation.coords; + + return geocoder.reverse(lat, lng).then(res => { + const result = res.results[0]; + console.debug('Result:', result); + data.currentUserLocation.openCageResult = result; + }) +} + +function geocodeNearestCity () { + console.debug('Geocoding nearest city...'); + const result = data.currentUserLocation.openCageResult; + let { city, town, village, hamlet, county } = result.components; + if (!city) { city = town || village || hamlet || county; } + const query = `${city}, ${result.components.state}, ${result.components['ISO_3166-1_alpha-2']}&no_record=1&min_confidence=3`; + + return geocoder.geocode(query).then(res => { + const result = res.results[0]; + console.debug('Result:', result); + data.currentCity.coords = { + lat: result.geometry.lat.toFixed(4), + lng: result.geometry.lng.toFixed(4) + }; + data.currentCity.openCageResult = result; + }) +} + +function geocodeLocationInput () { + console.debug('Geocoding supplied location...'); + const input = document.querySelector('input.location').value; + const query = `${input},&no_record=1&min_confidence=3`; + + return geocoder.geocode(query).then(res => { + return new Promise((resolve, reject) => { + const result = res.results[0]; + console.debug('Result:', result); + if (result) { + data.currentCity.coords = { + lat: result.geometry.lat.toFixed(4), + lng: result.geometry.lng.toFixed(4) + }; + data.currentCity.openCageResult = result; + resolve(); + } else { + reject('Nothing found'); + } + }); + }) +} + +function createGeoJSON () { + const res = data.currentCity.openCageResult; + const { city, town, village, hamlet, county } = res.components; + data.currentCity.geoJSON = { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [res.geometry.lng, res.geometry.lat] + }, + "city": city || town || village || hamlet, + "state": res.components.state, + "country": res.components.country, + "timezone": res.annotations.timezone + } + console.debug('Created GeoJSON object:', data.currentCity.geojson); +} + +async function fetchMapImage () { + const mapBoxToken = await remoteStorage.apiKeys.get('mapbox').then(c => c.token); + const zoomFactor = localStorage.getItem('rs-location:map-zoom-factor'); + const { lat, lng } = data.currentCity.coords; + const url = encodeURI(`https://api.mapbox.com/styles/${mapBoxStyle}/static/pin-s-harbor+fff(${lng},${lat})/${lng},${lat},${zoomFactor}/${imageSize}?access_token=${mapBoxToken}`) + console.debug('Fetching map image...'); + + return fetch(url) + .then(response => response.blob()) + .then(imageData => { + const url = URL.createObjectURL(imageData); + data.currentCity.imageBlob = imageData; + data.currentCity.imageObjectURL = url + + return fetch(url) + .then(res => res.arrayBuffer()) + .then(arrayBuffer => { + data.currentCity.imageArrayBuffer = arrayBuffer; + }) + }); +} + +function renderMapImage () { + const url = data.currentCity.imageObjectURL; + renderImage('section.current-city .map', url, '260x100'); +} + +function renderCoordinates () { + showElement('section.current-location'); + const coordsEl = document.querySelector('section.current-location .coords'); + const coords = data.currentUserLocation.coords; + coordsEl.innerHTML = `Latitude: ${coords.lat}
Longitude: ${coords.lng}`; +} + +function renderFormattedResult () { + const el = document.querySelector('section.current-location .formatted-result'); + el.innerHTML = data.currentUserLocation.openCageResult.formatted; +} + +function renderFormattedCityResult () { + showElement('section.current-city'); + const coordsEl = document.querySelector('section.current-city .coords'); + const coords = data.currentCity.coords; + coordsEl.innerHTML = `Latitude: ${coords.lat}
Longitude: ${coords.lng}`; + + const result = data.currentCity.openCageResult; + const el = document.querySelector('section.current-city .formatted-result'); + const { city, town, village, hamlet, county } = result.components; + el.innerHTML = `${city || town || village || hamlet || county}, ${result.components.country}`; +} diff --git a/modules/dom-helpers.mjs b/modules/dom-helpers.mjs new file mode 100644 index 0000000..eb9b8a5 --- /dev/null +++ b/modules/dom-helpers.mjs @@ -0,0 +1,28 @@ +function hideElement (selector) { + document.querySelector(selector) + .classList.add('hidden'); +} + +function showElement (selector) { + document.querySelector(selector) + .classList.remove('hidden'); +} + +function updateHTML (selector, content) { + document.querySelector(selector).innerHTML = content; +} + +function renderImage (parentSelector, imageUrl, size) { + const imageEl = document.createElement('img'); + imageEl.setAttribute("src", imageUrl); + if (size) { + const [ width, height ] = size.split('x'); + imageEl.setAttribute("width", width); + imageEl.setAttribute("height", height); + } + const parentEl = document.querySelector(parentSelector); + parentEl.innerHTML = ''; + parentEl.appendChild(imageEl); +} + +export { hideElement, showElement, updateHTML, renderImage }; diff --git a/modules/geocode.mjs b/modules/geocode.mjs new file mode 100644 index 0000000..909e6ef --- /dev/null +++ b/modules/geocode.mjs @@ -0,0 +1,20 @@ +export default class Geocoder { + + constructor (remoteStorage) { + this.rs = remoteStorage; + } + + async reverse (lat, lng) { + const q = `${lat}+${lng}&no_record=1&min_confidence=3`; + return this.geocode(q); + } + + async geocode (q) { + const openCageKey = await this.rs.apiKeys.get('opencage').then(c => c.token); + const response = await fetch( + `https://api.opencagedata.com/geocode/v1/json?key=${openCageKey}&q=${q}&no_record=1` + ); + return response.json(); + } + +} diff --git a/modules/profile.mjs b/modules/profile.mjs new file mode 100644 index 0000000..caf9c0b --- /dev/null +++ b/modules/profile.mjs @@ -0,0 +1,46 @@ +import { showElement, updateHTML, renderImage } from './dom-helpers.mjs'; + +function formattedCoordinates (geo) { + return `Latitude: ${geo.geometry.coordinates[1]}
` + + `Longitude: ${geo.geometry.coordinates[0]}
`; +} + +function formattedLocation (geo) { + return `${geo.city || geo.state}, ${geo.country}`; +} + +async function renderPublicProfile (geo) { + showElement('.profile.public'); + updateHTML('.profile.public .coords', formattedCoordinates(geo)); + updateHTML('.profile.public .formatted', formattedLocation(geo)); +} + +async function initializeProfileUpdates(remoteStorage, data) { + const privateClient = remoteStorage.scope('/profile/'); + const publicClient = remoteStorage.scope('/public/profile/'); + const profileUrl = await publicClient.getItemURL('current-location'); + const imageUrl = await publicClient.getItemURL('current-location.png'); + updateHTML('.profile.public .link', `Public URL`); + + await publicClient.getObject('current-location').then(async res => { + if (res) { + renderPublicProfile(res); + renderImage('.profile.public .map', imageUrl, '260x100'); + } + }) + + document.querySelector('.current-city button.publish').addEventListener('click', async () => { + const content = JSON.stringify(data.currentCity.geoJSON); + const mapImageData = data.currentCity.imageArrayBuffer; + + publicClient.storeFile('application/geo+json', 'current-location', content) + .then(renderPublicProfile(data.currentCity.geoJSON)); + + publicClient.storeFile('image/png', 'current-location.png', mapImageData) + .then(() => { + renderImage('.profile.public .map', `${imageUrl}?${new Date().getTime()}`, '260x100'); + }); + }) +} + +export default initializeProfileUpdates; diff --git a/modules/rs-module-api-keys.mjs b/modules/rs-module-api-keys.mjs new file mode 100644 index 0000000..6c99413 --- /dev/null +++ b/modules/rs-module-api-keys.mjs @@ -0,0 +1,23 @@ +const ApiKeys = { name: 'api-keys', builder: function (privateClient, publicClient) { + privateClient.declareType('credentials', { + // TODO add schema + }); + + return { + exports: { + set (serviceName, credentials = {}) { + return privateClient.storeObject('credentials', serviceName, credentials); + }, + + get (serviceName) { + return privateClient.getObject(serviceName); + }, + + remove (serviceName) { + return privateClient.remove(serviceName); + } + } + } +}}; + +export default ApiKeys; diff --git a/modules/settings.mjs b/modules/settings.mjs new file mode 100644 index 0000000..8f346c0 --- /dev/null +++ b/modules/settings.mjs @@ -0,0 +1,32 @@ +async function initializeSettings(remoteStorage) { + // + // API Keys (remoteStorage) + // + ['opencage', 'mapbox'].forEach(async service => { + let inputEl = document.querySelector(`input.api-key.${service}`); + + await remoteStorage.apiKeys.get(service).then(credentials => { + if (credentials) inputEl.value = credentials.token; + }) + + inputEl.addEventListener('change', e => { + if (e.target.value.length > 0) { + remoteStorage.apiKeys.set(service, { token: e.target.value }); + } else { + remoteStorage.apiKeys.remove(service); + } + }) + }); + + // + // Map settings (localStorage) + // + const zoomFactorSelectEl = document.querySelector('.settings .map-zoom-factor'); + const zoomFactor = localStorage.getItem('rs-location:map-zoom-factor'); + if (zoomFactor) zoomFactorSelectEl.value = zoomFactor; + zoomFactorSelectEl.addEventListener('change', e => { + localStorage.setItem('rs-location:map-zoom-factor', e.target.value); + }); +} + +export default initializeSettings; diff --git a/style.css b/style.css new file mode 100644 index 0000000..2df0eba --- /dev/null +++ b/style.css @@ -0,0 +1,13 @@ +body { + font-family: system-ui; +} + +main { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + grid-column-gap: 2rem; +} + +.hidden { + display: none; +} diff --git a/vendor/remotestorage.js b/vendor/remotestorage.js new file mode 100644 index 0000000..65e1f5f --- /dev/null +++ b/vendor/remotestorage.js @@ -0,0 +1,27 @@ +/*! remotestorage.js 1.2.2, https://remotestorage.io, MIT licensed */ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define("RemoteStorage",[],t):"object"==typeof exports?exports.RemoteStorage=t():e.RemoteStorage=t()}(this,function(){return function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}return r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=17)}([function(e,t,r){(function(t,r){function n(e){return(n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}var o={logError:function(e){"string"==typeof e?console.error(e):console.error(e.message,e.stack)},globalContext:"undefined"!=typeof window?window:"object"===("undefined"==typeof self?"undefined":n(self))?self:t,getGlobalContext:function(){return"undefined"!=typeof window?window:"object"===("undefined"==typeof self?"undefined":n(self))?self:t},extend:function(e){return Array.prototype.slice.call(arguments,1).forEach(function(t){for(var r in t)e[r]=t[r]}),e},containingFolder:function(e){if(""===e)return"/";if(!e)throw"Path not given!";return e.replace(/\/+/g,"/").replace(/[^\/]+\/?$/,"")},isFolder:function(e){return"/"===e.substr(-1)},isDocument:function(e){return!o.isFolder(e)},baseName:function(e){var t=e.split("/");return o.isFolder(e)?t[t.length-2]+"/":t[t.length-1]},cleanPath:function(e){return e.replace(/\/+/g,"/").split("/").map(encodeURIComponent).join("/").replace(/'/g,"%27")},bindAll:function(e){for(var t in this)"function"==typeof e[t]&&(e[t]=e[t].bind(e))},equal:function(e,t,r){var i;if(r=r||[],n(e)!==n(t))return!1;if("number"==typeof e||"boolean"==typeof e||"string"==typeof e)return e===t;if("function"==typeof e)return e.toString()===t.toString();if(e instanceof ArrayBuffer&&t instanceof ArrayBuffer&&(e=new Uint8Array(e),t=new Uint8Array(t)),e instanceof Array){if(e.length!==t.length)return!1;for(var s=0,a=e.length;s=0)continue;(u=r.slice()).push(t[i])}if(!o.equal(e[i],t[i],u))return!1}}return!0},deepClone:function(e){var t;return void 0===e?void 0:(function e(t,r){var o,i;if("object"===n(t)&&!Array.isArray(t)&&null!==t)for(o in t)"object"===n(t[o])&&null!==t[o]&&("[object ArrayBuffer]"===t[o].toString()?(r[o]=new ArrayBuffer(t[o].byteLength),i=new Int8Array(t[o]),new Int8Array(r[o]).set(i)):e(t[o],r[o]))}(e,t=JSON.parse(JSON.stringify(e))),t)},pathsFromRoot:function(e){for(var t=[e],r=e.replace(/\/$/,"").split("/");r.length>1;)r.pop(),t.push(r.join("/")+"/");return t},localStorageAvailable:function(){var e=o.getGlobalContext();if(!("localStorage"in e))return!1;try{return e.localStorage.setItem("rs-check",1),e.localStorage.removeItem("rs-check"),!0}catch(e){return!1}},getJSONFromLocalStorage:function(e){var t=o.getGlobalContext();try{return JSON.parse(t.localStorage.getItem(e))}catch(e){}},shouldBeTreatedAsBinary:function(e,t){return t&&t.match(/charset=binary/)||/[\x00-\x08\x0E-\x1F\uFFFD]/.test(e)},getTextFromArrayBuffer:function(e,n){return new Promise(function(i){if("undefined"==typeof Blob){var s=new r(new Uint8Array(e));i(s.toString(n))}else{var a;if(o.globalContext.BlobBuilder=o.globalContext.BlobBuilder||o.globalContext.WebKitBlobBuilder,void 0!==o.globalContext.BlobBuilder){var u=new t.BlobBuilder;u.append(e),a=u.getBlob()}else a=new Blob([e]);var c=new FileReader;"function"==typeof c.addEventListener?c.addEventListener("loadend",function(e){i(e.target.result)}):c.onloadend=function(e){i(e.target.result)},c.readAsText(a,n)}})}};e.exports=o}).call(this,r(10),r(18).Buffer)},function(e,t,r){var n=r(3);e.exports=function(){n.logging&&console.log.apply(console,arguments)}},function(e,t,r){var n=r(1),o={addEventListener:function(e,t){if("string"!=typeof e)throw new Error("Argument eventName should be a string");if("function"!=typeof t)throw new Error("Argument handler should be a function");n("[Eventhandling] Adding event listener",e),this._validateEvent(e),this._handlers[e].push(t)},removeEventListener:function(e,t){this._validateEvent(e);for(var r=this._handlers[e].length,n=0;n0&&(e.state=n)}else e[decodeURIComponent(r[0])]=decodeURIComponent(r[1]);return e},{})}var a,u=function e(t,r){var n=r.authURL,s=r.scope,a=r.redirectUri,u=r.clientId;if(o("[Authorize] authURL = ",n,"scope = ",s,"redirectUri = ",a,"clientId = ",u),!i.localStorageAvailable()&&"remotestorage"===t.backend){a+=a.indexOf("#")>0?"&":"#";var c={userAddress:t.remote.userAddress,href:t.remote.href,storageApi:t.remote.storageApi,properties:t.remote.properties};a+="rsDiscovery="+btoa(JSON.stringify(c))}var l=function(e,t,r,n){var o=t.indexOf("#"),i=e;return i+=e.indexOf("?")>0?"&":"?",i+="redirect_uri="+encodeURIComponent(t.replace(/#.*$/,"")),i+="&scope="+encodeURIComponent(r),i+="&client_id="+encodeURIComponent(n),-1!==o&&o+1!==t.length&&(i+="&state="+encodeURIComponent(t.substring(o+1))),i+="&response_type=token"}(n,a,s,u);if(i.globalContext.cordova)return e.openWindow(l,a,"location=yes,clearsessioncache=yes,clearcache=yes").then(function(e){t.remote.configure({token:e.access_token})});e.setLocation(l)};u.IMPLIED_FAKE_TOKEN=!1,u.Unauthorized=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};this.name="Unauthorized",this.message=void 0===e?"App authorization expired or revoked.":e,void 0!==t.code&&(this.code=t.code),this.stack=(new Error).stack},u.Unauthorized.prototype=Object.create(Error.prototype),u.Unauthorized.prototype.constructor=u.Unauthorized,u.getLocation=function(){return document.location},u.setLocation=function(e){if("string"==typeof e)document.location.href=e;else{if("object"!==n(e))throw"Invalid location "+e;document.location=e}},u.openWindow=function(e,t,r){return new Promise(function(n,o){var i=open(e,"_blank",r);if(!i||i.closed)return o("Authorization popup was blocked");var a=function(){return o("Authorization was canceled")};i.addEventListener("loadstart",function(e){if(0===e.url.indexOf(t)){i.removeEventListener("exit",a),i.close();var r=s(e.url);return r?n(r):o("Authorization error")}}),i.addEventListener("exit",a)})},u._rs_supported=function(){return"undefined"!=typeof document},u._rs_init=function(e){a=function(){var n=!1;if(r){if(r.error)throw"access_denied"===r.error?new u.Unauthorized("Authorization failed: access denied",{code:"access_denied"}):new u.Unauthorized("Authorization failed: ".concat(r.error));r.rsDiscovery&&e.remote.configure(r.rsDiscovery),r.access_token&&(e.remote.configure({token:r.access_token}),n=!0),r.remotestorage&&(e.connect(r.remotestorage),n=!0),r.state&&(t=u.getLocation(),u.setLocation(t.href.split("#")[0]+"#"+r.state))}n||e.remote.stopWaitingForToken()};var t,r=s();r&&((t=u.getLocation()).hash=""),e.on("features-loaded",a)},u._rs_cleanup=function(e){e.removeEventListener("features-loaded",a)},e.exports=u},function(e,t,r){function n(e){return(n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}var o=r(2),i=r(0),s=r(3),a=r(22),u=r(23),c=u.SchemaNotFound,l=function(e,t){if("/"!==t[t.length-1])throw"Not a folder: "+t;"/"===t&&(this.makePath=function(e){return("/"===e[0]?"":"/")+e}),this.storage=e,this.base=t;var r=this.base.split("/");r.length>2?this.moduleName=r[1]:this.moduleName="root",o(this,"change"),this.on=this.on.bind(this),e.onChange(this.base,this._fireChange.bind(this))};l.Types=u,l.prototype={scope:function(e){return new l(this.storage,this.makePath(e))},getListing:function(e,t){if("string"!=typeof e)e="";else if(e.length>0&&"/"!==e[e.length-1])return Promise.reject("Not a folder: "+e);return this.storage.get(this.makePath(e),t).then(function(e){return 404===e.statusCode?{}:e.body})},getAll:function(e,t){if("string"!=typeof e)e="";else if(e.length>0&&"/"!==e[e.length-1])return Promise.reject("Not a folder: "+e);return this.storage.get(this.makePath(e),t).then(function(r){if(404===r.statusCode)return{};if("object"===n(r.body)){var o=Object.keys(r.body);if(0===o.length)return{};var i=o.map(function(o){return this.storage.get(this.makePath(e+o),t).then(function(e){if("string"==typeof e.body)try{e.body=JSON.parse(e.body)}catch(e){}"object"===n(e.body)&&(r.body[o]=e.body)})}.bind(this));return Promise.all(i).then(function(){return r.body})}}.bind(this))},getFile:function(e,t){return"string"!=typeof e?Promise.reject("Argument 'path' of baseClient.getFile must be a string"):this.storage.get(this.makePath(e),t).then(function(e){return{data:e.body,contentType:e.contentType,revision:e.revision}})},storeFile:function(e,t,r){return"string"!=typeof e?Promise.reject("Argument 'mimeType' of baseClient.storeFile must be a string"):"string"!=typeof t?Promise.reject("Argument 'path' of baseClient.storeFile must be a string"):"string"!=typeof r&&"object"!==n(r)?Promise.reject("Argument 'body' of baseClient.storeFile must be a string, ArrayBuffer, or ArrayBufferView"):(this.storage.access.checkPathPermission(this.makePath(t),"rw")||console.warn("WARNING: Editing a document to which only read access ('r') was claimed"),this.storage.put(this.makePath(t),r,e).then(function(e){return 200===e.statusCode||201===e.statusCode?e.revision:Promise.reject("Request (PUT "+this.makePath(t)+") failed with status: "+e.statusCode)}.bind(this)))},getObject:function(e,t){return"string"!=typeof e?Promise.reject("Argument 'path' of baseClient.getObject must be a string"):this.storage.get(this.makePath(e),t).then(function(t){if("object"===n(t.body))return t.body;if("string"==typeof t.body)try{return JSON.parse(t.body)}catch(t){throw"Not valid JSON: "+this.makePath(e)}else if(void 0!==t.body&&200===t.statusCode)return Promise.reject("Not an object: "+this.makePath(e))}.bind(this))},storeObject:function(e,t,r){if("string"!=typeof e)return Promise.reject("Argument 'typeAlias' of baseClient.storeObject must be a string");if("string"!=typeof t)return Promise.reject("Argument 'path' of baseClient.storeObject must be a string");if("object"!==n(r))return Promise.reject("Argument 'object' of baseClient.storeObject must be an object");this._attachType(r,e);try{var o=this.validate(r);if(!o.valid)return Promise.reject(o)}catch(e){return Promise.reject(e)}return this.storage.put(this.makePath(t),JSON.stringify(r),"application/json; charset=UTF-8").then(function(e){return 200===e.statusCode||201===e.statusCode?e.revision:Promise.reject("Request (PUT "+this.makePath(t)+") failed with status: "+e.statusCode)}.bind(this))},remove:function(e){return"string"!=typeof e?Promise.reject("Argument 'path' of baseClient.remove must be a string"):(this.storage.access.checkPathPermission(this.makePath(e),"rw")||console.warn("WARNING: Removing a document to which only read access ('r') was claimed"),this.storage.delete(this.makePath(e)))},getItemURL:function(e){if("string"!=typeof e)throw"Argument 'path' of baseClient.getItemURL must be a string";return this.storage.connected?(e=this._cleanPath(this.makePath(e)),this.storage.remote.href+e):void 0},cache:function(e,t){if("string"!=typeof e)throw"Argument 'path' of baseClient.cache must be a string";if(void 0===t)t="ALL";else if("string"!=typeof t)throw"Argument 'strategy' of baseClient.cache must be a string or undefined";if("FLUSH"!==t&&"SEEN"!==t&&"ALL"!==t)throw'Argument \'strategy\' of baseclient.cache must be one of ["FLUSH", "SEEN", "ALL"]';return this.storage.caching.set(this.makePath(e),t),this},flush:function(e){return this.storage.local.flush(e)},declareType:function(e,t,r){r||(r=t,t=this._defaultTypeURI(e)),l.Types.declare(this.moduleName,e,t,r)},validate:function(e){var t=l.Types.getSchema(e["@context"]);if(t)return a.validateResult(e,t);throw new c(e["@context"])},schemas:{configurable:!0,get:function(){return l.Types.inScope(this.moduleName)}},_defaultTypeURI:function(e){return"http://remotestorage.io/spec/modules/"+encodeURIComponent(this.moduleName)+"/"+encodeURIComponent(e)},_attachType:function(e,t){e["@context"]=l.Types.resolveAlias(this.moduleName+"/"+t)||this._defaultTypeURI(t)},makePath:function(e){return this.base+(e||"")},_fireChange:function(e){s.changeEvents[e.origin]&&(["new","old","lastCommon"].forEach(function(t){if((!e[t+"ContentType"]||/^application\/(.*)json(.*)/.exec(e[t+"ContentType"]))&&"string"==typeof e[t+"Value"])try{e[t+"Value"]=JSON.parse(e[t+"Value"])}catch(e){}}),this._emit("change",e))},_cleanPath:i.cleanPath},l._rs_init=function(){},e.exports=l},function(e,t,r){"use strict";function n(e){return(n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}var o,i,s=r(1),a=r(0),u=r(2),c=r(4),l=r(3),h="remotestorage:wireclient",f={"draft-dejong-remotestorage-00":2,"draft-dejong-remotestorage-01":3,"draft-dejong-remotestorage-02":4,"https://www.w3.org/community/rww/wiki/read-write-web-00#simple":1};if("function"==typeof ArrayBufferView)i=function(e){return e&&e instanceof ArrayBufferView};else{var d=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array];i=function(e){for(var t=0;t<8;t++)if(e instanceof d[t])return!0;return!1}}var p=a.isFolder,m=a.cleanPath,y=a.shouldBeTreatedAsBinary,g=a.getJSONFromLocalStorage,v=a.getTextFromArrayBuffer;function b(e){return"string"!=typeof e?e:"*"===e?"*":'"'+e+'"'}function _(e){return"string"!=typeof e?e:e.replace(/^["']|["']$/g,"")}var w=function(e){if(this.rs=e,this.connected=!1,u(this,"connected","not-connected"),o){var t=g(h);t&&setTimeout(function(){this.configure(t)}.bind(this),0)}this._revisionCache={},this.connected&&setTimeout(this._emit.bind(this),0,"connected")};w.prototype={_request:function(e,t,r,n,o,i,a){if(("PUT"===e||"DELETE"===e)&&"/"===t[t.length-1])return Promise.reject("Don't "+e+" on directories!");var u,l=this;return r!==c.IMPLIED_FAKE_TOKEN&&(n.Authorization="Bearer "+r),this.rs._emit("wire-busy",{method:e,isFolder:p(t)}),w.request(e,t,{body:o,headers:n,responseType:"arraybuffer"}).then(function(r){if(l.online||(l.online=!0,l.rs._emit("network-online")),l.rs._emit("wire-done",{method:e,isFolder:p(t),success:!0}),o=r.status,[401,403,404,412].indexOf(o)>=0)return s("[WireClient] Error response status",r.status),u=i?_(r.getResponseHeader("ETag")):void 0,401===r.status&&l.rs._emit("error",new c.Unauthorized),Promise.resolve({statusCode:r.status,revision:u});if(function(e){return[201,204,304].indexOf(e)>=0}(r.status)||200===r.status&&"GET"!==e)return u=_(r.getResponseHeader("ETag")),s("[WireClient] Successful request",u),Promise.resolve({statusCode:r.status,revision:u});var n=r.getResponseHeader("Content-Type");u=i?_(r.getResponseHeader("ETag")):200===r.status?a:void 0;var o,h=function(e){var t,r="UTF-8";return e&&(t=e.match(/charset=(.+)$/))&&(r=t[1]),r}(n);return y(r.response,n)?(s("[WireClient] Successful request with unknown or binary mime-type",u),Promise.resolve({statusCode:r.status,body:r.response,contentType:n,revision:u})):v(r.response,h).then(function(e){return s("[WireClient] Successful request",u),Promise.resolve({statusCode:r.status,body:e,contentType:n,revision:u})})},function(r){return l.online&&(l.online=!1,l.rs._emit("network-offline")),l.rs._emit("wire-done",{method:e,isFolder:p(t),success:!1}),Promise.reject(r)})},configure:function(e){if("object"!==n(e))throw new Error("WireClient configure settings parameter should be an object");void 0!==e.userAddress&&(this.userAddress=e.userAddress),void 0!==e.href&&(this.href=e.href),void 0!==e.storageApi&&(this.storageApi=e.storageApi),void 0!==e.token&&(this.token=e.token),void 0!==e.properties&&(this.properties=e.properties),void 0!==this.storageApi&&(this._storageApi=f[this.storageApi]||5,this.supportsRevs=this._storageApi>=2),this.href&&this.token?(this.connected=!0,this.online=!0,this._emit("connected")):this.connected=!1,o&&(localStorage[h]=JSON.stringify({userAddress:this.userAddress,href:this.href,storageApi:this.storageApi,token:this.token,properties:this.properties}))},stopWaitingForToken:function(){this.connected||this._emit("not-connected")},get:function(e,t){var r=this;if(!this.connected)return Promise.reject("not connected (path: "+e+")");t||(t={});var o={};return this.supportsRevs&&t.ifNoneMatch&&(o["If-None-Match"]=b(t.ifNoneMatch)),this._request("GET",this.href+m(e),this.token,o,void 0,this.supportsRevs,this._revisionCache[e]).then(function(t){if(!p(e))return Promise.resolve(t);var o,i={};if(void 0!==t.body)try{t.body=JSON.parse(t.body)}catch(t){return Promise.reject("Folder description at "+r.href+m(e)+" is not JSON")}if(200===t.statusCode&&"object"===n(t.body)){if(0===Object.keys(t.body).length)t.statusCode=404;else if("http://remotestorage.io/spec/folder-description"===(o=t.body)["@context"]&&"object"===n(o.items)){for(var s in t.body.items)r._revisionCache[e+s]=t.body.items[s].ETag;i=t.body.items}else Object.keys(t.body).forEach(function(n){r._revisionCache[e+n]=t.body[n],i[n]={ETag:t.body[n]}});return t.body=i,Promise.resolve(t)}return Promise.resolve(t)})},put:function(e,t,r,n){if(!this.connected)return Promise.reject("not connected (path: "+e+")");n||(n={}),!r.match(/charset=/)&&(t instanceof ArrayBuffer||i(t))&&(r+="; charset=binary");var o={"Content-Type":r};return this.supportsRevs&&(n.ifMatch&&(o["If-Match"]=b(n.ifMatch)),n.ifNoneMatch&&(o["If-None-Match"]=b(n.ifNoneMatch))),this._request("PUT",this.href+m(e),this.token,o,t,this.supportsRevs)},delete:function(e,t){if(!this.connected)throw new Error("not connected (path: "+e+")");t||(t={});var r={};return this.supportsRevs&&t.ifMatch&&(r["If-Match"]=b(t.ifMatch)),this._request("DELETE",this.href+m(e),this.token,r,void 0,this.supportsRevs)}},w.isArrayBufferView=i,w.request=function(e,t,r){return"function"==typeof fetch?w._fetchRequest(e,t,r):"function"==typeof XMLHttpRequest?w._xhrRequest(e,t,r):(s("[WireClient] add a polyfill for fetch or XMLHttpRequest"),Promise.reject("[WireClient] add a polyfill for fetch or XMLHttpRequest"))},w._fetchRequest=function(e,t,r){var n,o,i={};"function"==typeof AbortController&&(o=new AbortController);var a=fetch(t,{method:e,headers:r.headers,body:r.body,signal:o?o.signal:void 0}).then(function(e){switch(s("[WireClient fetch]",e),e.headers.forEach(function(e,t){i[t.toUpperCase()]=e}),n={readyState:4,status:e.status,statusText:e.statusText,response:void 0,getResponseHeader:function(e){return i[e.toUpperCase()]||null},responseType:r.responseType,responseURL:t},r.responseType){case"arraybuffer":return e.arrayBuffer();case"blob":return e.blob();case"json":return e.json();case void 0:case"":case"text":return e.text();default:throw new Error("responseType 'document' is not currently supported using fetch")}}).then(function(e){return n.response=e,r.responseType&&"text"!==r.responseType||(n.responseText=e),n}),u=new Promise(function(e,t){setTimeout(function(){t("timeout"),o&&o.abort()},l.requestTimeout)});return Promise.race([a,u])},w._xhrRequest=function(e,t,r){return new Promise(function(o,a){s("[WireClient]",e,t);var u=!1,c=setTimeout(function(){u=!0,a("timeout")},l.requestTimeout),h=new XMLHttpRequest;if(h.open(e,t,!0),r.responseType&&(h.responseType=r.responseType),r.headers)for(var f in r.headers)h.setRequestHeader(f,r.headers[f]);h.onload=function(){u||(clearTimeout(c),o(h))},h.onerror=function(e){u||(clearTimeout(c),a(e))};var d=r.body;"object"===n(d)&&!i(d)&&d instanceof ArrayBuffer&&(d=new Uint8Array(d)),h.send(d)})},Object.defineProperty(w.prototype,"storageType",{get:function(){if(this.storageApi){var e=this.storageApi.match(/draft-dejong-(remotestorage-\d\d)/);return e?e[1]:"2012.04"}}}),w._rs_init=function(e){o=a.localStorageAvailable(),e.remote=new w(e),this.online=!0},w._rs_supported=function(){return"function"==typeof fetch||"function"==typeof XMLHttpRequest},w._rs_cleanup=function(){o&&delete localStorage[h]},e.exports=w},function(e,t,r){function n(e,t){return!t||"object"!==u(t)&&"function"!=typeof t?function(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}(e):t}function o(e){var t="function"==typeof Map?new Map:void 0;return(o=function(e){if(null===e||(r=e,-1===Function.toString.call(r).indexOf("[native code]")))return e;var r;if("function"!=typeof e)throw new TypeError("Super expression must either be null or a function");if(void 0!==t){if(t.has(e))return t.get(e);t.set(e,n)}function n(){return i(e,arguments,a(this).constructor)}return n.prototype=Object.create(e.prototype,{constructor:{value:n,enumerable:!1,writable:!0,configurable:!0}}),s(n,e)})(e)}function i(e,t,r){return(i=function(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],function(){})),!0}catch(e){return!1}}()?Reflect.construct:function(e,t,r){var n=[null];n.push.apply(n,t);var o=new(Function.bind.apply(e,n));return r&&s(o,r.prototype),o}).apply(null,arguments)}function s(e,t){return(s=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function a(e){return(a=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function u(e){return(u="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function c(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function l(e,t){for(var r=0;r0}},{key:"collectDiffTasks",value:function(){var e=this,t=0;return this.rs.local.forAllNodes(function(r){t>100||(e.isCorrupt(r)?(w("[Sync] WARNING: corrupt node in local cache",r),"object"===u(r)&&r.path&&(e.addTask(r.path),t++)):e.needsFetch(r)&&e.rs.access.checkPathPermission(r.path,"r")?(e.addTask(r.path),t++):m(r.path)&&e.needsPush(r)&&e.rs.access.checkPathPermission(r.path,"rw")&&(e.addTask(r.path),t++))}).then(function(){return t},function(e){throw e})}},{key:"inConflict",value:function(e){return e.local&&e.remote&&(void 0!==e.remote.body||e.remote.itemsMap)}},{key:"needsRefresh",value:function(e){return!!e.common&&(!e.common.timestamp||this.now()-e.common.timestamp>E.syncInterval)}},{key:"needsFetch",value:function(e){return!!this.inConflict(e)||(!(!e.common||void 0!==e.common.itemsMap||void 0!==e.common.body)||!(!e.remote||void 0!==e.remote.itemsMap||void 0!==e.remote.body))}},{key:"needsPush",value:function(e){return!this.inConflict(e)&&(!(!e.local||e.push)||void 0)}},{key:"needsRemotePut",value:function(e){return e.local&&e.local.body}},{key:"needsRemoteDelete",value:function(e){return e.local&&!1===e.local.body}},{key:"getParentPath",value:function(e){var t=e.match(/^(.*\/)([^\/]+\/?)$/);if(t)return t[1];throw new Error('Not a valid path: "'+e+'"')}},{key:"deleteChildPathsFromTasks",value:function(){for(var e in this._tasks)for(var t=v(e),r=1;r=e))return!0;return r>=e}},{key:"collectTasks",value:function(e){var t=this;return this.hasTasks()||this.stopped?Promise.resolve():this.collectDiffTasks().then(function(r){return r||!1===e?Promise.resolve():t.collectRefreshTasks()},function(e){throw e})}},{key:"addTask",value:function(e,t){this._tasks[e]||(this._tasks[e]=[]),"function"==typeof t&&this._tasks[e].push(t)}},{key:"sync",value:function(){var e=this;return this.done=!1,this.doTasks()?Promise.resolve():this.collectTasks().then(function(){try{e.doTasks()}catch(e){w("[Sync] doTasks error",e)}},function(e){throw w("[Sync] Sync error",e),new Error("Local cache unavailable")})}}])&&l(t.prototype,r),n&&l(t,n),e}();R.SyncError=function(e){function t(e){var r;c(this,t),(r=n(this,a(t).call(this))).name="SyncError";var o="Sync failed: ";return"object"===u(e)&&"message"in e?(o+=e.message,r.stack=e.stack,r.originalError=e):o+=e,r.message=o,r}return function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&s(e,t)}(t,o(Error)),t}(),e.exports=R},function(e,t,r){function n(e){return(n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}var o=r(0),i=r(3),s=r(1),a=o.isFolder,u=o.isDocument,c=o.deepClone;function l(e){if("object"===n(e)&&"string"==typeof e.path)if(a(e.path)){if(e.local&&e.local.itemsMap)return e.local;if(e.common&&e.common.itemsMap)return e.common}else{if(e.local){if(e.local.body&&e.local.contentType)return e.local;if(!1===e.local.body)return}if(e.common&&e.common.body&&e.common.contentType)return e.common;if(e.body&&e.contentType)return{body:e.body,contentType:e.contentType}}}function h(e,t){var r;for(r in e){if(e[r]&&e[r].remote)return!0;var n=l(e[r]);if(n&&n.timestamp&&(new Date).getTime()-n.timestamp<=t)return!1;if(!n)return!0}return!0}var f=o.pathsFromRoot;function d(e){var t={path:e,common:{}};return a(e)&&(t.common.itemsMap={}),t}function p(e,t){return e.common||(e.common={itemsMap:{}}),e.common.itemsMap||(e.common.itemsMap={}),e.local||(e.local=c(e.common)),e.local.itemsMap||(e.local.itemsMap=e.common.itemsMap),e.local.itemsMap[t]=!0,e}var m={get:function(e,t,r){return"number"==typeof t?this.getNodes(f(e)).then(function(n){var o=l(n[e]);return h(n,t)?r(e):o?{statusCode:200,body:o.body||o.itemsMap,contentType:o.contentType}:{statusCode:404}}):this.getNodes([e]).then(function(t){var r=l(t[e]);if(r){if(a(e))for(var n in r.itemsMap)r.itemsMap.hasOwnProperty(n)&&!1===r.itemsMap[n]&&delete r.itemsMap[n];return{statusCode:200,body:r.body||r.itemsMap,contentType:r.contentType}}return{statusCode:404}})},put:function(e,t,r){var n=f(e);return this._updateNodes(n,function(e,n){try{for(var o=0,i=e.length;o0)break}else console.error("Cannot delete non-existing node "+o)}return t})},flush:function(e){var t=this;return t._getAllDescendentPaths(e).then(function(e){return t.getNodes(e)}).then(function(e){for(var r in e){var n=e[r];n&&n.common&&n.local&&t._emitChange({path:n.path,origin:"local",oldValue:!1===n.local.body?void 0:n.local.body,newValue:!1===n.common.body?void 0:n.common.body}),e[r]=void 0}return t.setNodes(e)})},_emitChange:function(e){i.changeEvents[e.origin]&&this._emit("change",e)},fireInitial:function(){if(i.changeEvents.local){var e=this;e.forAllNodes(function(t){var r;u(t.path)&&(r=l(t))&&e._emitChange({path:t.path,origin:"local",oldValue:void 0,oldContentType:void 0,newValue:r.body,newContentType:r.contentType})}).then(function(){e._emit("local-events-done")})}},onDiff:function(e){this.diffHandler=e},migrate:function(e){return"object"!==n(e)||e.common||(e.common={},"string"==typeof e.path?"/"===e.path.substr(-1)&&"object"===n(e.body)&&(e.common.itemsMap=e.body):(e.local||(e.local={}),e.local.body=e.body,e.local.contentType=e.contentType)),e},_updateNodesRunning:!1,_updateNodesQueued:[],_updateNodes:function(e,t){return new Promise(function(r,n){this._doUpdateNodes(e,t,{resolve:r,reject:n})}.bind(this))},_doUpdateNodes:function(e,t,r){var n=this;n._updateNodesRunning?n._updateNodesQueued.push({paths:e,cb:t,promise:r}):(n._updateNodesRunning=!0,n.getNodes(e).then(function(i){var s,a=c(i),l=[],h=o.equal;for(var f in i=t(e,i))h(s=i[f],a[f])?delete i[f]:u(f)&&(h(s.local.body,s.local.previousBody)&&s.local.contentType===s.local.previousContentType||l.push({path:f,origin:"window",oldValue:s.local.previousBody,newValue:!1===s.local.body?void 0:s.local.body,oldContentType:s.local.previousContentType,newContentType:s.local.contentType}),delete s.local.previousBody,delete s.local.previousContentType);n.setNodes(i).then(function(){n._emitChangeEvents(l),r.resolve({statusCode:200})})}).then(function(){return Promise.resolve()},function(e){r.reject(e)}).then(function(){n._updateNodesRunning=!1;var e=n._updateNodesQueued.shift();e&&n._doUpdateNodes(e.paths,e.cb,e.promise)}))},_emitChangeEvents:function(e){for(var t=0,r=e.length;t1e3&&e<36e5}v.Authorize=h,v.SyncError=f.SyncError,v.Unauthorized=h.Unauthorized,v.DiscoveryError=u.DiscoveryError,v.prototype={loadModules:function(){l.modules.forEach(this.addModule.bind(this))},authorize:function(e){this.access.setStorageType(this.remote.storageApi),void 0===e.scope&&(e.scope=this.access.scopeParameter),e.redirectUri=m.cordova?l.cordovaRedirectUri:String(h.getLocation()),void 0===e.clientId&&(e.clientId=e.redirectUri.match(/^(https?:\/\/[^\/]+)/)[0]),h(this,e)},impliedauth:function(e,t){e=this.remote.storageApi,t=String(document.location),d("ImpliedAuth proceeding due to absent authURL; storageApi = "+e+" redirectUri = "+t),this.remote.configure({token:h.IMPLIED_FAKE_TOKEN}),document.location=t},connect:function(e,t){var r=this;if(this.setBackend("remotestorage"),e.indexOf("@")<0)this._emit("error",new v.DiscoveryError("User address doesn't contain an @."));else{if(m.cordova){if("string"!=typeof l.cordovaRedirectUri)return void this._emit("error",new v.DiscoveryError("Please supply a custom HTTPS redirect URI for your Cordova app"));if(!m.cordova.InAppBrowser)return void this._emit("error",new v.DiscoveryError("Please include the InAppBrowser Cordova plugin to enable OAuth"))}this.remote.configure({userAddress:e}),this._emit("connecting");var n=setTimeout(function(){this._emit("error",new v.DiscoveryError("No storage information found for this user address."))}.bind(this),l.discoveryTimeout);u(e).then(function(o){if(clearTimeout(n),r._emit("authing"),o.userAddress=e,r.remote.configure(o),!r.remote.connected)if(o.authURL)if(void 0===t)r.authorize({authURL:o.authURL});else{if("string"!=typeof t)throw new Error("Supplied bearer token must be a string");d("Skipping authorization sequence and connecting with known token"),r.remote.configure({token:t})}else r.impliedauth()},function(){clearTimeout(n),r._emit("error",new v.DiscoveryError("No storage information found for this user address."))})}},reconnect:function(){this.remote.configure({token:null}),"remotestorage"===this.backend?this.connect(this.remote.userAddress):this.remote.connect()},disconnect:function(){this.remote&&this.remote.configure({userAddress:null,href:null,storageApi:null,token:null,properties:null}),this._setGPD({get:this._pendingGPD("get"),put:this._pendingGPD("put"),delete:this._pendingGPD("delete")});var e=this._cleanups.length,t=0,r=function(){++t>=e&&(this._init(),d("Done cleaning up, emitting disconnected and disconnect events"),this._emit("disconnected"))}.bind(this);e>0?this._cleanups.forEach(function(e){var t=e(this);"object"===n(t)&&"function"==typeof t.then?t.then(r):r()}.bind(this)):r()},setBackend:function(e){this.backend=e,o&&(e?localStorage.setItem("remotestorage:backend",e):localStorage.removeItem("remotestorage:backend"))},onChange:function(e,t){this._pathHandlers.change[e]||(this._pathHandlers.change[e]=[]),this._pathHandlers.change[e].push(t)},enableLog:function(){l.logging=!0},disableLog:function(){l.logging=!1},log:function(){d.apply(v,arguments)},setApiKeys:function(e){var t=this,r=["googledrive","dropbox"];if("object"!==n(e)||!Object.keys(e).every(function(e){return r.includes(e)}))return console.error("setApiKeys() was called with invalid arguments"),!1;Object.keys(e).forEach(function(r){var n=e[r];if(n){switch(r){case"dropbox":t.apiKeys.dropbox={appKey:n},void 0!==t.dropbox&&t.dropbox.clientId===n||s._rs_init(t);break;case"googledrive":t.apiKeys.googledrive={clientId:n},void 0!==t.googledrive&&t.googledrive.clientId===n||a._rs_init(t)}return!0}delete t.apiKeys[r]}),o&&localStorage.setItem("remotestorage:api-keys",JSON.stringify(this.apiKeys))},setCordovaRedirectUri:function(e){if("string"!=typeof e||!e.match(/http(s)?:\/\//))throw new Error("Cordova redirect URI must be a URI string");l.cordovaRedirectUri=e},_init:p.loadFeatures,features:p.features,loadFeature:p.loadFeature,featureSupported:p.featureSupported,featureDone:p.featureDone,featuresDone:p.featuresDone,featuresLoaded:p.featuresLoaded,featureInitialized:p.featureInitialized,featureFailed:p.featureFailed,hasFeature:p.hasFeature,_setCachingModule:p._setCachingModule,_collectCleanupFunctions:p._collectCleanupFunctions,_fireReady:p._fireReady,initFeature:p.initFeature,_setGPD:function(e,t){function r(e){return function(){return e.apply(t,arguments).then(function(e){return 403!==e.statusCode&&401!==e.statusCode||this._emit("error",new h.Unauthorized),Promise.resolve(e)}.bind(this))}}this.get=r(e.get),this.put=r(e.put),this.delete=r(e.delete)},_pendingGPD:function(e){return function(){var t=Array.prototype.slice.call(arguments);return new Promise(function(r,n){this._pending.push({method:e,args:t,promise:{resolve:r,reject:n}})}.bind(this))}.bind(this)},_processPending:function(){this._pending.forEach(function(e){try{this[e.method].apply(this,e.args).then(e.promise.resolve,e.promise.reject)}catch(t){e.promise.reject(t)}}.bind(this)),this._pending=[]},_bindChange:function(e){e.on("change",this._dispatchEvent.bind(this,"change"))},_dispatchEvent:function(e,t){var r=this;Object.keys(this._pathHandlers[e]).forEach(function(n){var o=n.length;t.path.substr(0,o)===n&&r._pathHandlers[e][n].forEach(function(e){var o={};for(var i in t)o[i]=t[i];o.relativePath=t.path.replace(new RegExp("^"+n),"");try{e(o)}catch(e){console.error("'change' handler failed: ",e,e.stack),r._emit("error",e)}})})},scope:function(e){if("string"!=typeof e)throw"Argument 'path' of baseClient.scope must be a string";if(!this.access.checkPathPermission(e,"r")){var t=e.replace(/(['\\])/g,"\\$1");console.warn("WARNING: please call remoteStorage.access.claim('"+t+"', 'r') (read only) or remoteStorage.access.claim('"+t+"', 'rw') (read/write) first")}return new c(this,e)},getSyncInterval:function(){return l.syncInterval},setSyncInterval:function(e){if(!b(e))throw e+" is not a valid sync interval";var t=l.syncInterval;l.syncInterval=parseInt(e,10),this._emit("sync-interval-change",{oldValue:t,newValue:e})},getBackgroundSyncInterval:function(){return l.backgroundSyncInterval},setBackgroundSyncInterval:function(e){if(!b(e))throw e+" is not a valid sync interval";var t=l.backgroundSyncInterval;l.backgroundSyncInterval=parseInt(e,10),this._emit("sync-interval-change",{oldValue:t,newValue:e})},getCurrentSyncInterval:function(){return l.isBackground?l.backgroundSyncInterval:l.syncInterval},getRequestTimeout:function(){return l.requestTimeout},setRequestTimeout:function(e){l.requestTimeout=parseInt(e,10)},syncCycle:function(){this.sync&&!this.sync.stopped&&(this.on("sync-done",function(){d("[Sync] Sync done. Setting timer to",this.getCurrentSyncInterval()),this.sync&&!this.sync.stopped&&(this._syncTimer&&(clearTimeout(this._syncTimer),this._syncTimer=void 0),this._syncTimer=setTimeout(this.sync.sync.bind(this.sync),this.getCurrentSyncInterval()))}.bind(this)),this.sync.sync())},startSync:function(){return l.cache?(this.sync.stopped=!1,this.syncStopped=!1,this.sync.sync()):(console.warn("Nothing to sync, because caching is disabled."),Promise.resolve())},stopSync:function(){clearTimeout(this._syncTimer),this._syncTimer=void 0,this.sync?(d("[Sync] Stopping sync"),this.sync.stopped=!0):(d("[Sync] Will instantiate sync stopped"),this.syncStopped=!0)}},v.util=i,Object.defineProperty(v.prototype,"connected",{get:function(){return this.remote.connected}});var _=r(15);Object.defineProperty(v.prototype,"access",{get:function(){var e=new _;return Object.defineProperty(this,"access",{value:e}),e},configurable:!0});var w=r(16);Object.defineProperty(v.prototype,"caching",{configurable:!0,get:function(){var e=new w;return Object.defineProperty(this,"caching",{value:e}),e}}),e.exports=v,r(32)},function(e,t){var r;r=function(){return this}();try{r=r||new Function("return this")()}catch(e){"object"==typeof window&&(r=window)}e.exports=r},function(e,t,r){function n(e){return(n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}var o,i=r(4),s=r(5),a=r(6),u=r(0),c=r(2),l=r(24),h=r(7),f="remotestorage:dropbox",d=u.isFolder,p=u.cleanPath,m=u.shouldBeTreatedAsBinary,y=u.getJSONFromLocalStorage,g=u.getTextFromArrayBuffer,v=function(e){return p("/remotestorage/"+e).replace(/\/$/,"")},b=function(e,t){return new RegExp("^"+t.join("\\/")+"(\\/|$)").test(e.error_summary)},_=function(e){return e instanceof ArrayBuffer||a.isArrayBufferView(e)},w=function(e){if(this.rs=e,this.connected=!1,this.rs=e,this._initialFetchDone=!1,c(this,"connected","not-connected"),this.clientId=e.apiKeys.dropbox.appKey,this._revCache=new l("rev"),this._fetchDeltaCursor=null,this._fetchDeltaPromise=null,this._itemRefs={},o=u.localStorageAvailable()){var t=y(f);t&&this.configure(t),this._itemRefs=y("".concat(f,":shares"))||{}}this.connected&&setTimeout(this._emit.bind(this),0,"connected")};function P(e){e._dropboxOrigSync||(e._dropboxOrigSync=e.sync.sync.bind(e.sync),e.sync.sync=function(){return this.dropbox.fetchDelta.apply(this.dropbox,arguments).then(e._dropboxOrigSync,function(t){e._emit("error",new h.SyncError(t)),e._emit("sync-done")})}.bind(e))}function E(e){e._dropboxOrigSyncCycle&&(e.syncCycle=e._dropboxOrigSyncCycle,delete e._dropboxOrigSyncCycle)}function S(e){!function(e){e._origRemote||(e._origRemote=e.remote,e.remote=e.dropbox)}(e),e.sync?P(e):function(e){var t=arguments;e._dropboxOrigSyncCycle||(e._dropboxOrigSyncCycle=e.syncCycle,e.syncCycle=function(){if(!e.sync)throw new Error("expected sync to be initialized by now");P(e),e._dropboxOrigSyncCycle(t),E(e)})}(e),function(e){e._origBaseClientGetItemURL||(e._origBaseClientGetItemURL=s.prototype.getItemURL,s.prototype.getItemURL=function(){throw new Error("getItemURL is not implemented for Dropbox yet")})}(e)}function T(e){!function(e){e._origRemote&&(e.remote=e._origRemote,delete e._origRemote)}(e),function(e){e._dropboxOrigSync&&(e.sync.sync=e._dropboxOrigSync,delete e._dropboxOrigSync)}(e),function(e){e._origBaseClientGetItemURL&&(s.prototype.getItemURL=e._origBaseClientGetItemURL,delete e._origBaseClientGetItemURL)}(e),E(e)}w.prototype={online:!0,connect:function(){this.rs.setBackend("dropbox"),this.token?S(this.rs):this.rs.authorize({authURL:"https://www.dropbox.com/oauth2/authorize",scope:"",clientId:this.clientId})},configure:function(e){void 0!==e.userAddress&&(this.userAddress=e.userAddress),void 0!==e.token&&(this.token=e.token);var t=function(){o&&localStorage.setItem(f,JSON.stringify({userAddress:this.userAddress,token:this.token}))},r=function(){this.connected=!1,o&&localStorage.removeItem(f)};this.token?(this.connected=!0,this.userAddress?(this._emit("connected"),t.apply(this)):this.info().then(function(e){this.userAddress=e.email,this._emit("connected"),t.apply(this)}.bind(this)).catch(function(){r.apply(this),this.rs._emit("error",new Error("Could not fetch user info."))}.bind(this))):r.apply(this)},stopWaitingForToken:function(){this.connected||this._emit("not-connected")},_getFolder:function(e){var t=this._revCache,r=this,n=function(n){var i,s;if(200!==n.status&&409!==n.status)return Promise.reject("Unexpected response status: "+n.status);try{i=JSON.parse(n.responseText)}catch(e){return Promise.reject(e)}return 409===n.status?b(i,["path","not_found"])?Promise.resolve({}):Promise.reject(new Error("API returned an error: "+i.error_summary)):(s=i.entries.reduce(function(n,o){var i="folder"===o[".tag"],s=o.path_lower.split("/").slice(-1)[0]+(i?"/":"");return i?n[s]={ETag:t.get(e+s)}:(n[s]={ETag:o.rev},r._revCache.set(e+s,o.rev)),n},{}),i.has_more?o(i.cursor).then(function(e){return Object.assign(s,e)}):Promise.resolve(s))},o=function(e){var t={body:{cursor:e}};return r._request("POST","https://api.dropboxapi.com/2/files/list_folder/continue",t).then(n)};return this._request("POST","https://api.dropboxapi.com/2/files/list_folder",{body:{path:v(e)}}).then(n).then(function(r){return Promise.resolve({statusCode:200,body:r,contentType:"application/json; charset=UTF-8",revision:t.get(e)})})},get:function(e,t){var r=this;if(!this.connected)return Promise.reject("not connected (path: "+e+")");var n=this,o=this._revCache.get(e);if(null===o)return Promise.resolve({statusCode:404});if(t&&t.ifNoneMatch){if(!this._initialFetchDone)return this.fetchDelta().then(function(){return r.get(e,t)});if(o&&o===t.ifNoneMatch)return Promise.resolve({statusCode:304})}if("/"===e.substr(-1))return this._getFolder(e,t);var i={headers:{"Dropbox-API-Arg":JSON.stringify({path:v(e)})},responseType:"arraybuffer"};return t&&t.ifNoneMatch&&(i.headers["If-None-Match"]=t.ifNoneMatch),this._request("GET","https://content.dropboxapi.com/2/files/download",i).then(function(t){var r,o,i,s,a=t.status;return 200!==a&&409!==a?Promise.resolve({statusCode:a}):(r=t.getResponseHeader("Dropbox-API-Result"),g(t.response,"UTF-8").then(function(u){o=u,409===a&&(r=o);try{r=JSON.parse(r)}catch(e){return Promise.reject(e)}if(409===a)return b(r,["path","not_found"])?{statusCode:404}:Promise.reject(new Error('API error while downloading file ("'+e+'"): '+r.error_summary));if(i=t.getResponseHeader("Content-Type"),s=r.rev,n._revCache.set(e,s),n._shareIfNeeded(e),m(u,i))o=t.response;else try{o=JSON.parse(o),i="application/json; charset=UTF-8"}catch(e){}return{statusCode:a,body:o,contentType:i,revision:s}}))})},put:function(e,t,r,n){var o=this;if(!this.connected)throw new Error("not connected (path: "+e+")");var i=this._revCache.get(e);if(n&&n.ifMatch&&i&&i!==n.ifMatch)return Promise.resolve({statusCode:412,revision:i});if(n&&"*"===n.ifNoneMatch&&i&&"rev"!==i)return Promise.resolve({statusCode:412,revision:i});if(!r.match(/charset=/)&&_(t)&&(r+="; charset=binary"),t.length>157286400)return Promise.reject(new Error("Cannot upload file larger than 150MB"));var s=n&&(n.ifMatch||"*"===n.ifNoneMatch),a={body:t,contentType:r,path:e};return(s?this._getMetadata(e).then(function(e){return n&&"*"===n.ifNoneMatch&&e?Promise.resolve({statusCode:412,revision:e.rev}):n&&n.ifMatch&&e&&e.rev!==n.ifMatch?Promise.resolve({statusCode:412,revision:e.rev}):o._uploadSimple(a)}):o._uploadSimple(a)).then(function(t){return o._shareIfNeeded(e),t})},delete:function(e,t){var r=this;if(!this.connected)throw new Error("not connected (path: "+e+")");var n=this._revCache.get(e);return t&&t.ifMatch&&n&&t.ifMatch!==n?Promise.resolve({statusCode:412,revision:n}):t&&t.ifMatch?this._getMetadata(e).then(function(n){return t&&t.ifMatch&&n&&n.rev!==t.ifMatch?Promise.resolve({statusCode:412,revision:n.rev}):r._deleteSimple(e)}):this._deleteSimple(e)},_shareIfNeeded:function(e){e.match(/^\/public\/.*[^\/]$/)&&void 0===this._itemRefs[e]&&this.share(e)},share:function(e){var t=this,r={body:{path:v(e)}};return this._request("POST","https://api.dropboxapi.com/2/sharing/create_shared_link_with_settings",r).then(function(r){if(200!==r.status&&409!==r.status)return Promise.reject(new Error("Invalid response status:"+r.status));var n;try{n=JSON.parse(r.responseText)}catch(e){return Promise.reject(new Error("Invalid response body: "+r.responseText))}return 409===r.status?b(n,["shared_link_already_exists"])?t._getSharedLink(e):Promise.reject(new Error("API error: "+n.error_summary)):Promise.resolve(n.url)}).then(function(r){return t._itemRefs[e]=r,o&&localStorage.setItem(f+":shares",JSON.stringify(t._itemRefs)),Promise.resolve(r)},function(t){return t.message='Sharing Dropbox file or folder ("'+e+'") failed: '+t.message,Promise.reject(t)})},info:function(){return this._request("POST","https://api.dropboxapi.com/2/users/get_current_account",{}).then(function(e){var t=e.responseText;try{t=JSON.parse(t)}catch(e){return Promise.reject(new Error("Could not query current account info: Invalid API response: "+t))}return Promise.resolve({email:t.email})})},_request:function(e,t,r){var o=this;return r.headers||(r.headers={}),r.headers.Authorization="Bearer "+this.token,"object"!==n(r.body)||_(r.body)||(r.body=JSON.stringify(r.body),r.headers["Content-Type"]="application/json; charset=UTF-8"),this.rs._emit("wire-busy",{method:e,isFolder:d(t)}),a.request.call(this,e,t,r).then(function(n){return n&&503===n.status?(o.online&&(o.online=!1,o.rs._emit("network-offline")),setTimeout(o._request(e,t,r),3210)):(o.online||(o.online=!0,o.rs._emit("network-online")),o.rs._emit("wire-done",{method:e,isFolder:d(t),success:!0}),Promise.resolve(n))},function(r){return o.online&&(o.online=!1,o.rs._emit("network-offline")),o.rs._emit("wire-done",{method:e,isFolder:d(t),success:!1}),Promise.reject(r)})},fetchDelta:function(){var e=this;if(this._fetchDeltaPromise)return this._fetchDeltaPromise;var t=Array.prototype.slice.call(arguments),r=this;return this._fetchDeltaPromise=function e(n){var o,s="https://api.dropboxapi.com/2/files/list_folder";return"string"==typeof n?(s+="/continue",o={cursor:n}):o={path:"/remotestorage",recursive:!0,include_deleted:!0},r._request("POST",s,{body:o}).then(function(o){if(401===o.status)return r.rs._emit("error",new i.Unauthorized),Promise.resolve(t);if(200!==o.status&&409!==o.status)return Promise.reject(new Error("Invalid response status: "+o.status));var s;try{s=JSON.parse(o.responseText)}catch(e){return Promise.reject(new Error("Invalid response body: "+o.responseText))}if(409===o.status){if(!b(s,["path","not_found"]))return Promise.reject(new Error("API returned an error: "+s.error_summary));s={cursor:null,entries:[],has_more:!1}}if(n||r._revCache.deactivatePropagation(),s.entries.forEach(function(e){var t=e.path_lower.substr("/remotestorage".length);"deleted"===e[".tag"]?(r._revCache.delete(t),r._revCache.delete(t+"/")):"file"===e[".tag"]&&r._revCache.set(t,e.rev)}),r._fetchDeltaCursor=s.cursor,s.has_more)return e(s.cursor);r._revCache.activatePropagation(),r._initialFetchDone=!0}).catch(function(e){return"timeout"===e||e instanceof ProgressEvent?Promise.resolve():Promise.reject(e)})}(r._fetchDeltaCursor).catch(function(t){return"object"===n(t)&&"message"in t?t.message="Dropbox: fetchDelta: "+t.message:t="Dropbox: fetchDelta: ".concat(t),e._fetchDeltaPromise=null,Promise.reject(t)}).then(function(){return e._fetchDeltaPromise=null,Promise.resolve(t)}),this._fetchDeltaPromise},_getMetadata:function(e){var t={path:v(e)};return this._request("POST","https://api.dropboxapi.com/2/files/get_metadata",{body:t}).then(function(e){if(200!==e.status&&409!==e.status)return Promise.reject(new Error("Invalid response status:"+e.status));var t;try{t=JSON.parse(e.responseText)}catch(t){return Promise.reject(new Error("Invalid response body: "+e.responseText))}return 409===e.status?b(t,["path","not_found"])?Promise.resolve():Promise.reject(new Error("API error: "+t.error_summary)):Promise.resolve(t)}).then(void 0,function(t){return t.message='Could not load metadata for file or folder ("'+e+'"): '+t.message,Promise.reject(t)})},_uploadSimple:function(e){var t=this,r={path:v(e.path),mode:{".tag":"overwrite"},mute:!0};return e.ifMatch&&(r.mode={".tag":"update",update:e.ifMatch}),this._request("POST","https://content.dropboxapi.com/2/files/upload",{body:e.body,headers:{"Content-Type":"application/octet-stream","Dropbox-API-Arg":JSON.stringify(r)}}).then(function(r){if(200!==r.status&&409!==r.status)return Promise.resolve({statusCode:r.status});var n=r.responseText;try{n=JSON.parse(n)}catch(e){return Promise.reject(new Error("Invalid API result: "+n))}return 409===r.status?b(n,["path","conflict"])?t._getMetadata(e.path).then(function(e){return Promise.resolve({statusCode:412,revision:e.rev})}):Promise.reject(new Error("API error: "+n.error_summary)):(t._revCache.set(e.path,n.rev),Promise.resolve({statusCode:r.status,revision:n.rev}))})},_deleteSimple:function(e){var t=this,r={path:v(e)};return this._request("POST","https://api.dropboxapi.com/2/files/delete",{body:r}).then(function(e){if(200!==e.status&&409!==e.status)return Promise.resolve({statusCode:e.status});var t=e.responseText;try{t=JSON.parse(t)}catch(e){return Promise.reject(new Error("Invalid response body: "+t))}return 409===e.status?b(t,["path_lookup","not_found"])?Promise.resolve({statusCode:404}):Promise.reject(new Error("API error: "+t.error_summary)):Promise.resolve({statusCode:200})}).then(function(r){return 200!==r.statusCode&&404!==r.statusCode||(t._revCache.delete(e),delete t._itemRefs[e]),Promise.resolve(r)},function(t){return t.message='Could not delete Dropbox file or folder ("'+e+'"): '+t.message,Promise.reject(t)})},_getSharedLink:function(e){var t={body:{path:v(e),direct_only:!0}};return this._request("POST","https://api.dropbox.com/2/sharing/list_shared_links",t).then(function(e){if(200!==e.status&&409!==e.status)return Promise.reject(new Error("Invalid response status: "+e.status));var t;try{t=JSON.parse(e.responseText)}catch(t){return Promise.reject(new Error("Invalid response body: "+e.responseText))}return 409===e.status?Promise.reject(new Error("API error: "+e.error_summary)):t.links.length?Promise.resolve(t.links[0].url):Promise.reject(new Error("No links returned"))},function(t){return t.message='Could not get link to a shared file or folder ("'+e+'"): '+t.message,Promise.reject(t)})}},w._rs_init=function(e){o=u.localStorageAvailable(),e.apiKeys.dropbox&&(e.dropbox=new w(e)),"dropbox"===e.backend&&S(e)},w._rs_supported=function(){return!0},w._rs_cleanup=function(e){T(e),o&&localStorage.removeItem(f),e.setBackend(void 0)},e.exports=w},function(e,t,r){var n=r(2),o="undefined"!=typeof window?"browser":"node",i={},s=function(){return i};s.isBrowser=function(){return"browser"===o},s.isNode=function(){return"node"===o},s.goBackground=function(){s._emit("background")},s.goForeground=function(){s._emit("foreground")},s._rs_init=function(){function e(){document[i.hiddenProperty]?s.goBackground():s.goForeground()}n(s,"background","foreground"),"browser"===o&&(void 0!==document.hidden?(i.hiddenProperty="hidden",i.visibilityChangeEvent="visibilitychange"):void 0!==document.mozHidden?(i.hiddenProperty="mozHidden",i.visibilityChangeEvent="mozvisibilitychange"):void 0!==document.msHidden?(i.hiddenProperty="msHidden",i.visibilityChangeEvent="msvisibilitychange"):void 0!==document.webkitHidden&&(i.hiddenProperty="webkitHidden",i.visibilityChangeEvent="webkitvisibilitychange"),document.addEventListener(i.visibilityChangeEvent,e,!1),e())},s._rs_cleanup=function(){},e.exports=s},function(e,t,r){function n(e){return(n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}var o,i=r(5),s=r(6),a=r(2),u=r(0),c="https://www.googleapis.com",l="remotestorage:googledrive",h="/remotestorage",f=u.isFolder,d=u.cleanPath,p=u.shouldBeTreatedAsBinary,m=u.getJSONFromLocalStorage,y=u.getTextFromArrayBuffer;function g(e){return"/"===e.substr(-1)&&(e=e.substr(0,e.length-1)),decodeURIComponent(e)}function v(e){return e.replace(/[^\/]+\/?$/,"")}function b(e){var t=e.split("/");return"/"===e.substr(-1)?t[t.length-2]+"/":t[t.length-1]}function _(e){return d("".concat(h,"/").concat(e))}function w(e){return e.replace(/^["'](.*)["']$/,"$1")}var P=function(e){this.maxAge=e,this._items={}};P.prototype={get:function(e){var t=this._items[e],r=(new Date).getTime();return t&&t.t>=r-this.maxAge?t.v:void 0},set:function(e,t){this._items[e]={v:t,t:(new Date).getTime()}}};var E=function(e,t){if(a(this,"connected","not-connected"),this.rs=e,this.clientId=t,this._fileIdCache=new P(300),o=u.localStorageAvailable()){var r=m(l);r&&this.configure(r)}};E.prototype={connected:!1,online:!0,configure:function(e){var t=this;void 0!==e.userAddress&&(this.userAddress=e.userAddress),void 0!==e.token&&(this.token=e.token);var r=function(){o&&localStorage.setItem(l,JSON.stringify({userAddress:this.userAddress,token:this.token}))},n=function(){this.connected=!1,delete this.token,o&&localStorage.removeItem(l)};this.token?(this.connected=!0,this.userAddress?(this._emit("connected"),r.apply(this)):this.info().then(function(e){t.userAddress=e.user.emailAddress,t._emit("connected"),r.apply(t)}).catch(function(){n.apply(t),t.rs._emit("error",new Error("Could not fetch user info."))})):n.apply(this)},connect:function(){this.rs.setBackend("googledrive"),this.rs.authorize({authURL:"https://accounts.google.com/o/oauth2/auth",scope:"https://www.googleapis.com/auth/drive",clientId:this.clientId})},stopWaitingForToken:function(){this.connected||this._emit("not-connected")},get:function(e,t){return"/"===e.substr(-1)?this._getFolder(_(e),t):this._getFile(_(e),t)},put:function(e,t,r,n){var o=this,i=_(e);function s(e){if(e.status>=200&&e.status<300){var t=JSON.parse(e.responseText),r=w(t.etag);return Promise.resolve({statusCode:200,contentType:t.mimeType,revision:r})}return 412===e.status?Promise.resolve({statusCode:412,revision:"conflict"}):Promise.reject("PUT failed with status "+e.status+" ("+e.responseText+")")}return this._getFileId(i).then(function(e){return e?n&&"*"===n.ifNoneMatch?s({status:412}):o._updateFile(e,i,t,r,n).then(s):o._createFile(i,t,r,n).then(s)})},delete:function(e,t){var r=this,o=_(e);return this._getFileId(o).then(function(e){return e?r._getMeta(e).then(function(o){var i;return"object"===n(o)&&"string"==typeof o.etag&&(i=w(o.etag)),t&&t.ifMatch&&t.ifMatch!==i?{statusCode:412,revision:i}:r._request("DELETE",c+"/drive/v2/files/"+e,{}).then(function(e){return 200===e.status||204===e.status?{statusCode:200}:Promise.reject("Delete failed: "+e.status+" ("+e.responseText+")")})}):Promise.resolve({statusCode:200})})},info:function(){return this._request("GET","https://www.googleapis.com/drive/v2/about?fields=user",{}).then(function(e){try{var t=JSON.parse(e.responseText);return Promise.resolve(t)}catch(e){return Promise.reject(e)}})},_updateFile:function(e,t,r,n,o){var i=this,s={mimeType:n},a={"Content-Type":"application/json; charset=UTF-8"};return o&&o.ifMatch&&(a["If-Match"]='"'+o.ifMatch+'"'),this._request("PUT",c+"/upload/drive/v2/files/"+e+"?uploadType=resumable",{body:JSON.stringify(s),headers:a}).then(function(e){return 412===e.status?e:i._request("PUT",e.getResponseHeader("Location"),{body:n.match(/^application\/json/)?JSON.stringify(r):r})})},_createFile:function(e,t,r){var n=this;return this._getParentId(e).then(function(o){var i={title:g(b(e)),mimeType:r,parents:[{kind:"drive#fileLink",id:o}]};return n._request("POST",c+"/upload/drive/v2/files?uploadType=resumable",{body:JSON.stringify(i),headers:{"Content-Type":"application/json; charset=UTF-8"}}).then(function(e){return n._request("POST",e.getResponseHeader("Location"),{body:r.match(/^application\/json/)?JSON.stringify(t):t})})})},_getFile:function(e,t){var r=this;return this._getFileId(e).then(function(e){return r._getMeta(e).then(function(e){var o;if("object"===n(e)&&"string"==typeof e.etag&&(o=w(e.etag)),t&&t.ifNoneMatch&&o===t.ifNoneMatch)return Promise.resolve({statusCode:304});if(!e.downloadUrl){if(!e.exportLinks||!e.exportLinks["text/html"])return Promise.resolve({statusCode:200,body:"",contentType:e.mimeType,revision:o});e.mimeType+=";export=text/html",e.downloadUrl=e.exportLinks["text/html"]}return r._request("GET",e.downloadUrl,{responseType:"arraybuffer"}).then(function(t){return y(t.response,"UTF-8").then(function(r){var n=r;if(e.mimeType.match(/^application\/json/))try{n=JSON.parse(n)}catch(e){}else p(r,e.mimeType)&&(n=t.response);return{statusCode:200,body:n,contentType:e.mimeType,revision:o}})})})})},_getFolder:function(e){var t=this;return this._getFileId(e).then(function(r){var n,o,i,s;return r?(n="'"+r+"' in parents","items(downloadUrl,etag,fileSize,id,mimeType,title)",t._request("GET",c+"/drive/v2/files?q="+encodeURIComponent(n)+"&fields="+encodeURIComponent("items(downloadUrl,etag,fileSize,id,mimeType,title)")+"&maxResults=1000",{}).then(function(r){if(200!==r.status)return Promise.reject("request failed or something: "+r.status);try{o=JSON.parse(r.responseText)}catch(e){return Promise.reject("non-JSON response from GoogleDrive")}s={};var n=!0,a=!1,u=void 0;try{for(var c,l=o.items[Symbol.iterator]();!(n=(c=l.next()).done);n=!0){var h=c.value;i=w(h.etag),"application/vnd.google-apps.folder"===h.mimeType?(t._fileIdCache.set(e+h.title+"/",h.id),s[h.title+"/"]={ETag:i}):(t._fileIdCache.set(e+h.title,h.id),s[h.title]={ETag:i,"Content-Type":h.mimeType,"Content-Length":h.fileSize})}}catch(e){a=!0,u=e}finally{try{n||null==l.return||l.return()}finally{if(a)throw u}}return Promise.resolve({statusCode:200,body:s,contentType:"application/json; charset=UTF-8",revision:void 0})})):Promise.resolve({statusCode:404})})},_getParentId:function(e){var t=this,r=v(e);return this._getFileId(r).then(function(e){return e?Promise.resolve(e):t._createFolder(r)})},_createFolder:function(e){var t=this;return this._getParentId(e).then(function(r){return t._request("POST",c+"/drive/v2/files",{body:JSON.stringify({title:g(b(e)),mimeType:"application/vnd.google-apps.folder",parents:[{id:r}]}),headers:{"Content-Type":"application/json; charset=UTF-8"}}).then(function(e){var t=JSON.parse(e.responseText);return Promise.resolve(t.id)})})},_getFileId:function(e){var t,r=this;return"/"===e?Promise.resolve("root"):(t=this._fileIdCache.get(e))?Promise.resolve(t):this._getFolder(v(e)).then(function(){return(t=r._fileIdCache.get(e))?Promise.resolve(t):"/"===e.substr(-1)?r._createFolder(e).then(function(){return r._getFileId(e)}):Promise.resolve()})},_getMeta:function(e){return this._request("GET",c+"/drive/v2/files/"+e,{}).then(function(t){return 200===t.status?Promise.resolve(JSON.parse(t.responseText)):Promise.reject("request (getting metadata for "+e+") failed with status: "+t.status)})},_request:function(e,t,r){var n=this;return r.headers||(r.headers={}),r.headers.Authorization="Bearer "+this.token,this.rs._emit("wire-busy",{method:e,isFolder:f(t)}),s.request.call(this,e,t,r).then(function(r){return r&&401===r.status?void n.connect():(n.online||(n.online=!0,n.rs._emit("network-online")),n.rs._emit("wire-done",{method:e,isFolder:f(t),success:!0}),Promise.resolve(r))},function(r){return n.online&&(n.online=!1,n.rs._emit("network-offline")),n.rs._emit("wire-done",{method:e,isFolder:f(t),success:!1}),Promise.reject(r)})}},E._rs_init=function(e){var t,r=e.apiKeys.googledrive;r&&(e.googledrive=new E(e,r.clientId),"googledrive"===e.backend&&(e._origRemote=e.remote,e.remote=e.googledrive,(t=e)._origBaseClientGetItemURL||(t._origBaseClientGetItemURL=i.prototype.getItemURL,i.prototype.getItemURL=function(){throw new Error("getItemURL is not implemented for Google Drive yet")})))},E._rs_supported=function(){return!0},E._rs_cleanup=function(e){var t;e.setBackend(void 0),e._origRemote&&(e.remote=e._origRemote,delete e._origRemote),(t=e)._origBaseClientGetItemURL&&(i.prototype.getItemURL=t._origBaseClientGetItemURL,delete t._origBaseClientGetItemURL)},e.exports=E},function(e,t,r){"use strict";function n(e){return(n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}var o,i=r(1),s=r(0),a=r(25),u={},c=function(e){return new Promise(function(t,r){return e in u?t(u[e]):new a({tls_only:!1,uri_fallback:!0,request_timeout:5e3}).lookup(e,function(s,a){if(s)return r(s);if("object"!==n(a.idx.links.remotestorage)||"number"!=typeof a.idx.links.remotestorage.length||a.idx.links.remotestorage.length<=0)return i("[Discover] WebFinger record for "+e+" does not have remotestorage defined in the links section ",JSON.stringify(a.json)),r("WebFinger record for "+e+" does not have remotestorage defined in the links section.");var c=a.idx.links.remotestorage[0],l=c.properties["http://tools.ietf.org/html/rfc6749#section-4.2"]||c.properties["auth-endpoint"],h=c.properties["http://remotestorage.io/spec/version"]||c.type;return u[e]={href:c.href,storageApi:h,authURL:l,properties:c.properties},o&&(localStorage["remotestorage:discover"]=JSON.stringify({cache:u})),t(u[e])})})};(c.DiscoveryError=function(e){this.name="DiscoveryError",this.message=e,this.stack=(new Error).stack}).prototype=Object.create(Error.prototype),c.DiscoveryError.prototype.constructor=c.DiscoveryError,c._rs_init=function(){if(o=s.localStorageAvailable()){var e;try{e=JSON.parse(localStorage["remotestorage:discover"])}catch(e){}e&&(u=e.cache)}},c._rs_supported=function(){return!!s.globalContext.XMLHttpRequest},c._rs_cleanup=function(){o&&delete localStorage["remotestorage:discover"]},e.exports=c},function(e,t){var r=function(){this.reset()};r.prototype={claim:function(e,t){if("string"!=typeof e||-1!==e.indexOf("/")||0===e.length)throw new Error("Scope should be a non-empty string without forward slashes");if(!t.match(/^rw?$/))throw new Error("Mode should be either 'r' or 'rw'");this._adjustRootPaths(e),this.scopeModeMap[e]=t},get:function(e){return this.scopeModeMap[e]},remove:function(e){var t,r={};for(t in this.scopeModeMap)r[t]=this.scopeModeMap[t];for(t in this.reset(),delete r[e],r)this.set(t,r[t])},checkPermission:function(e,t){var r=this.get(e);return r&&("r"===t||"rw"===r)},checkPathPermission:function(e,t){return!!this.checkPermission("*",t)||!!this.checkPermission(this._getModuleName(e),t)},reset:function(){this.rootPaths=[],this.scopeModeMap={}},_getModuleName:function(e){if("/"!==e[0])throw new Error("Path should start with a slash");var t=e.replace(/^\/public/,"").match(/^\/([^\/]*)\//);return t?t[1]:"*"},_adjustRootPaths:function(e){"*"in this.scopeModeMap||"*"===e?this.rootPaths=["/"]:e in this.scopeModeMap||(this.rootPaths.push("/"+e+"/"),this.rootPaths.push("/public/"+e+"/"))},_scopeNameForParameter:function(e){if("*"===e.name&&this.storageType){if("2012.04"===this.storageType)return"";if(this.storageType.match(/remotestorage-0[01]/))return"root"}return e.name},setStorageType:function(e){this.storageType=e}},Object.defineProperty(r.prototype,"scopes",{get:function(){return Object.keys(this.scopeModeMap).map(function(e){return{name:e,mode:this.scopeModeMap[e]}}.bind(this))}}),Object.defineProperty(r.prototype,"scopeParameter",{get:function(){return this.scopes.map(function(e){return this._scopeNameForParameter(e)+":"+e.mode}.bind(this)).join(" ")}}),r._rs_init=function(){},e.exports=r},function(e,t,r){var n=r(0),o=r(1),i=n.containingFolder,s=function(){this.reset()};s.prototype={pendingActivations:[],set:function(e,t){if("string"!=typeof e)throw new Error("path should be a string");if(!n.isFolder(e))throw new Error("path should be a folder");if(this._remoteStorage&&this._remoteStorage.access&&!this._remoteStorage.access.checkPathPermission(e,"r"))throw new Error('No access to path "'+e+'". You have to claim access to it first.');if(!t.match(/^(FLUSH|SEEN|ALL)$/))throw new Error("strategy should be 'FLUSH', 'SEEN', or 'ALL'");this._rootPaths[e]=t,"ALL"===t&&(this.activateHandler?this.activateHandler(e):this.pendingActivations.push(e))},enable:function(e){this.set(e,"ALL")},disable:function(e){this.set(e,"FLUSH")},onActivate:function(e){var t;for(o("[Caching] Setting activate handler",e,this.pendingActivations),this.activateHandler=e,t=0;t + * @license MIT + */ +var n=r(19),o=r(20),i=r(21);function s(){return u.TYPED_ARRAY_SUPPORT?2147483647:1073741823}function a(e,t){if(s()=s())throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+s().toString(16)+" bytes");return 0|e}function p(e,t){if(u.isBuffer(e))return e.length;if("undefined"!=typeof ArrayBuffer&&"function"==typeof ArrayBuffer.isView&&(ArrayBuffer.isView(e)||e instanceof ArrayBuffer))return e.byteLength;"string"!=typeof e&&(e=""+e);var r=e.length;if(0===r)return 0;for(var n=!1;;)switch(t){case"ascii":case"latin1":case"binary":return r;case"utf8":case"utf-8":case void 0:return B(e).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*r;case"hex":return r>>>1;case"base64":return q(e).length;default:if(n)return B(e).length;t=(""+t).toLowerCase(),n=!0}}function m(e,t,r){var n=e[t];e[t]=e[r],e[r]=n}function y(e,t,r,n,o){if(0===e.length)return-1;if("string"==typeof r?(n=r,r=0):r>2147483647?r=2147483647:r<-2147483648&&(r=-2147483648),r=+r,isNaN(r)&&(r=o?0:e.length-1),r<0&&(r=e.length+r),r>=e.length){if(o)return-1;r=e.length-1}else if(r<0){if(!o)return-1;r=0}if("string"==typeof t&&(t=u.from(t,n)),u.isBuffer(t))return 0===t.length?-1:g(e,t,r,n,o);if("number"==typeof t)return t&=255,u.TYPED_ARRAY_SUPPORT&&"function"==typeof Uint8Array.prototype.indexOf?o?Uint8Array.prototype.indexOf.call(e,t,r):Uint8Array.prototype.lastIndexOf.call(e,t,r):g(e,[t],r,n,o);throw new TypeError("val must be string, number or Buffer")}function g(e,t,r,n,o){var i,s=1,a=e.length,u=t.length;if(void 0!==n&&("ucs2"===(n=String(n).toLowerCase())||"ucs-2"===n||"utf16le"===n||"utf-16le"===n)){if(e.length<2||t.length<2)return-1;s=2,a/=2,u/=2,r/=2}function c(e,t){return 1===s?e[t]:e.readUInt16BE(t*s)}if(o){var l=-1;for(i=r;ia&&(r=a-u),i=r;i>=0;i--){for(var h=!0,f=0;fo&&(n=o):n=o;var i=t.length;if(i%2!=0)throw new TypeError("Invalid hex string");n>i/2&&(n=i/2);for(var s=0;s>8,o=r%256,i.push(o),i.push(n);return i}(t,e.length-r),e,r,n)}function S(e,t,r){return 0===t&&r===e.length?n.fromByteArray(e):n.fromByteArray(e.slice(t,r))}function T(e,t,r){r=Math.min(e.length,r);for(var n=[],o=t;o239?4:c>223?3:c>191?2:1;if(o+h<=r)switch(h){case 1:c<128&&(l=c);break;case 2:128==(192&(i=e[o+1]))&&(u=(31&c)<<6|63&i)>127&&(l=u);break;case 3:i=e[o+1],s=e[o+2],128==(192&i)&&128==(192&s)&&(u=(15&c)<<12|(63&i)<<6|63&s)>2047&&(u<55296||u>57343)&&(l=u);break;case 4:i=e[o+1],s=e[o+2],a=e[o+3],128==(192&i)&&128==(192&s)&&128==(192&a)&&(u=(15&c)<<18|(63&i)<<12|(63&s)<<6|63&a)>65535&&u<1114112&&(l=u)}null===l?(l=65533,h=1):l>65535&&(l-=65536,n.push(l>>>10&1023|55296),l=56320|1023&l),n.push(l),o+=h}return function(e){var t=e.length;if(t<=A)return String.fromCharCode.apply(String,e);var r="",n=0;for(;nthis.length)return"";if((void 0===r||r>this.length)&&(r=this.length),r<=0)return"";if((r>>>=0)<=(t>>>=0))return"";for(e||(e="utf8");;)switch(e){case"hex":return O(this,t,r);case"utf8":case"utf-8":return T(this,t,r);case"ascii":return R(this,t,r);case"latin1":case"binary":return k(this,t,r);case"base64":return S(this,t,r);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return I(this,t,r);default:if(n)throw new TypeError("Unknown encoding: "+e);e=(e+"").toLowerCase(),n=!0}}.apply(this,arguments)},u.prototype.equals=function(e){if(!u.isBuffer(e))throw new TypeError("Argument must be a Buffer");return this===e||0===u.compare(this,e)},u.prototype.inspect=function(){var e="",r=t.INSPECT_MAX_BYTES;return this.length>0&&(e=this.toString("hex",0,r).match(/.{2}/g).join(" "),this.length>r&&(e+=" ... ")),""},u.prototype.compare=function(e,t,r,n,o){if(!u.isBuffer(e))throw new TypeError("Argument must be a Buffer");if(void 0===t&&(t=0),void 0===r&&(r=e?e.length:0),void 0===n&&(n=0),void 0===o&&(o=this.length),t<0||r>e.length||n<0||o>this.length)throw new RangeError("out of range index");if(n>=o&&t>=r)return 0;if(n>=o)return-1;if(t>=r)return 1;if(this===e)return 0;for(var i=(o>>>=0)-(n>>>=0),s=(r>>>=0)-(t>>>=0),a=Math.min(i,s),c=this.slice(n,o),l=e.slice(t,r),h=0;ho)&&(r=o),e.length>0&&(r<0||t<0)||t>this.length)throw new RangeError("Attempt to write outside buffer bounds");n||(n="utf8");for(var i=!1;;)switch(n){case"hex":return v(this,e,t,r);case"utf8":case"utf-8":return b(this,e,t,r);case"ascii":return _(this,e,t,r);case"latin1":case"binary":return w(this,e,t,r);case"base64":return P(this,e,t,r);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return E(this,e,t,r);default:if(i)throw new TypeError("Unknown encoding: "+n);n=(""+n).toLowerCase(),i=!0}},u.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};var A=4096;function R(e,t,r){var n="";r=Math.min(e.length,r);for(var o=t;on)&&(r=n);for(var o="",i=t;ir)throw new RangeError("Trying to access beyond buffer length")}function M(e,t,r,n,o,i){if(!u.isBuffer(e))throw new TypeError('"buffer" argument must be a Buffer instance');if(t>o||te.length)throw new RangeError("Index out of range")}function N(e,t,r,n){t<0&&(t=65535+t+1);for(var o=0,i=Math.min(e.length-r,2);o>>8*(n?o:1-o)}function x(e,t,r,n){t<0&&(t=4294967295+t+1);for(var o=0,i=Math.min(e.length-r,4);o>>8*(n?o:3-o)&255}function U(e,t,r,n,o,i){if(r+n>e.length)throw new RangeError("Index out of range");if(r<0)throw new RangeError("Index out of range")}function j(e,t,r,n,i){return i||U(e,0,r,4),o.write(e,t,r,n,23,4),r+4}function L(e,t,r,n,i){return i||U(e,0,r,8),o.write(e,t,r,n,52,8),r+8}u.prototype.slice=function(e,t){var r,n=this.length;if((e=~~e)<0?(e+=n)<0&&(e=0):e>n&&(e=n),(t=void 0===t?n:~~t)<0?(t+=n)<0&&(t=0):t>n&&(t=n),t0&&(o*=256);)n+=this[e+--t]*o;return n},u.prototype.readUInt8=function(e,t){return t||C(e,1,this.length),this[e]},u.prototype.readUInt16LE=function(e,t){return t||C(e,2,this.length),this[e]|this[e+1]<<8},u.prototype.readUInt16BE=function(e,t){return t||C(e,2,this.length),this[e]<<8|this[e+1]},u.prototype.readUInt32LE=function(e,t){return t||C(e,4,this.length),(this[e]|this[e+1]<<8|this[e+2]<<16)+16777216*this[e+3]},u.prototype.readUInt32BE=function(e,t){return t||C(e,4,this.length),16777216*this[e]+(this[e+1]<<16|this[e+2]<<8|this[e+3])},u.prototype.readIntLE=function(e,t,r){e|=0,t|=0,r||C(e,t,this.length);for(var n=this[e],o=1,i=0;++i=(o*=128)&&(n-=Math.pow(2,8*t)),n},u.prototype.readIntBE=function(e,t,r){e|=0,t|=0,r||C(e,t,this.length);for(var n=t,o=1,i=this[e+--n];n>0&&(o*=256);)i+=this[e+--n]*o;return i>=(o*=128)&&(i-=Math.pow(2,8*t)),i},u.prototype.readInt8=function(e,t){return t||C(e,1,this.length),128&this[e]?-1*(255-this[e]+1):this[e]},u.prototype.readInt16LE=function(e,t){t||C(e,2,this.length);var r=this[e]|this[e+1]<<8;return 32768&r?4294901760|r:r},u.prototype.readInt16BE=function(e,t){t||C(e,2,this.length);var r=this[e+1]|this[e]<<8;return 32768&r?4294901760|r:r},u.prototype.readInt32LE=function(e,t){return t||C(e,4,this.length),this[e]|this[e+1]<<8|this[e+2]<<16|this[e+3]<<24},u.prototype.readInt32BE=function(e,t){return t||C(e,4,this.length),this[e]<<24|this[e+1]<<16|this[e+2]<<8|this[e+3]},u.prototype.readFloatLE=function(e,t){return t||C(e,4,this.length),o.read(this,e,!0,23,4)},u.prototype.readFloatBE=function(e,t){return t||C(e,4,this.length),o.read(this,e,!1,23,4)},u.prototype.readDoubleLE=function(e,t){return t||C(e,8,this.length),o.read(this,e,!0,52,8)},u.prototype.readDoubleBE=function(e,t){return t||C(e,8,this.length),o.read(this,e,!1,52,8)},u.prototype.writeUIntLE=function(e,t,r,n){(e=+e,t|=0,r|=0,n)||M(this,e,t,r,Math.pow(2,8*r)-1,0);var o=1,i=0;for(this[t]=255&e;++i=0&&(i*=256);)this[t+o]=e/i&255;return t+r},u.prototype.writeUInt8=function(e,t,r){return e=+e,t|=0,r||M(this,e,t,1,255,0),u.TYPED_ARRAY_SUPPORT||(e=Math.floor(e)),this[t]=255&e,t+1},u.prototype.writeUInt16LE=function(e,t,r){return e=+e,t|=0,r||M(this,e,t,2,65535,0),u.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8):N(this,e,t,!0),t+2},u.prototype.writeUInt16BE=function(e,t,r){return e=+e,t|=0,r||M(this,e,t,2,65535,0),u.TYPED_ARRAY_SUPPORT?(this[t]=e>>>8,this[t+1]=255&e):N(this,e,t,!1),t+2},u.prototype.writeUInt32LE=function(e,t,r){return e=+e,t|=0,r||M(this,e,t,4,4294967295,0),u.TYPED_ARRAY_SUPPORT?(this[t+3]=e>>>24,this[t+2]=e>>>16,this[t+1]=e>>>8,this[t]=255&e):x(this,e,t,!0),t+4},u.prototype.writeUInt32BE=function(e,t,r){return e=+e,t|=0,r||M(this,e,t,4,4294967295,0),u.TYPED_ARRAY_SUPPORT?(this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e):x(this,e,t,!1),t+4},u.prototype.writeIntLE=function(e,t,r,n){if(e=+e,t|=0,!n){var o=Math.pow(2,8*r-1);M(this,e,t,r,o-1,-o)}var i=0,s=1,a=0;for(this[t]=255&e;++i>0)-a&255;return t+r},u.prototype.writeIntBE=function(e,t,r,n){if(e=+e,t|=0,!n){var o=Math.pow(2,8*r-1);M(this,e,t,r,o-1,-o)}var i=r-1,s=1,a=0;for(this[t+i]=255&e;--i>=0&&(s*=256);)e<0&&0===a&&0!==this[t+i+1]&&(a=1),this[t+i]=(e/s>>0)-a&255;return t+r},u.prototype.writeInt8=function(e,t,r){return e=+e,t|=0,r||M(this,e,t,1,127,-128),u.TYPED_ARRAY_SUPPORT||(e=Math.floor(e)),e<0&&(e=255+e+1),this[t]=255&e,t+1},u.prototype.writeInt16LE=function(e,t,r){return e=+e,t|=0,r||M(this,e,t,2,32767,-32768),u.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8):N(this,e,t,!0),t+2},u.prototype.writeInt16BE=function(e,t,r){return e=+e,t|=0,r||M(this,e,t,2,32767,-32768),u.TYPED_ARRAY_SUPPORT?(this[t]=e>>>8,this[t+1]=255&e):N(this,e,t,!1),t+2},u.prototype.writeInt32LE=function(e,t,r){return e=+e,t|=0,r||M(this,e,t,4,2147483647,-2147483648),u.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8,this[t+2]=e>>>16,this[t+3]=e>>>24):x(this,e,t,!0),t+4},u.prototype.writeInt32BE=function(e,t,r){return e=+e,t|=0,r||M(this,e,t,4,2147483647,-2147483648),e<0&&(e=4294967295+e+1),u.TYPED_ARRAY_SUPPORT?(this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e):x(this,e,t,!1),t+4},u.prototype.writeFloatLE=function(e,t,r){return j(this,e,t,!0,r)},u.prototype.writeFloatBE=function(e,t,r){return j(this,e,t,!1,r)},u.prototype.writeDoubleLE=function(e,t,r){return L(this,e,t,!0,r)},u.prototype.writeDoubleBE=function(e,t,r){return L(this,e,t,!1,r)},u.prototype.copy=function(e,t,r,n){if(r||(r=0),n||0===n||(n=this.length),t>=e.length&&(t=e.length),t||(t=0),n>0&&n=this.length)throw new RangeError("sourceStart out of bounds");if(n<0)throw new RangeError("sourceEnd out of bounds");n>this.length&&(n=this.length),e.length-t=0;--o)e[o+t]=this[o+r];else if(i<1e3||!u.TYPED_ARRAY_SUPPORT)for(o=0;o>>=0,r=void 0===r?this.length:r>>>0,e||(e=0),"number"==typeof e)for(i=t;i55295&&r<57344){if(!o){if(r>56319){(t-=3)>-1&&i.push(239,191,189);continue}if(s+1===n){(t-=3)>-1&&i.push(239,191,189);continue}o=r;continue}if(r<56320){(t-=3)>-1&&i.push(239,191,189),o=r;continue}r=65536+(o-55296<<10|r-56320)}else o&&(t-=3)>-1&&i.push(239,191,189);if(o=null,r<128){if((t-=1)<0)break;i.push(r)}else if(r<2048){if((t-=2)<0)break;i.push(r>>6|192,63&r|128)}else if(r<65536){if((t-=3)<0)break;i.push(r>>12|224,r>>6&63|128,63&r|128)}else{if(!(r<1114112))throw new Error("Invalid code point");if((t-=4)<0)break;i.push(r>>18|240,r>>12&63|128,r>>6&63|128,63&r|128)}}return i}function q(e){return n.toByteArray(function(e){if((e=function(e){return e.trim?e.trim():e.replace(/^\s+|\s+$/g,"")}(e).replace(D,"")).length<2)return"";for(;e.length%4!=0;)e+="=";return e}(e))}function J(e,t,r,n){for(var o=0;o=t.length||o>=e.length);++o)t[o+r]=e[o];return o}}).call(this,r(10))},function(e,t,r){"use strict";t.byteLength=function(e){var t=c(e),r=t[0],n=t[1];return 3*(r+n)/4-n},t.toByteArray=function(e){for(var t,r=c(e),n=r[0],s=r[1],a=new i(function(e,t,r){return 3*(t+r)/4-r}(0,n,s)),u=0,l=s>0?n-4:n,h=0;h>16&255,a[u++]=t>>8&255,a[u++]=255&t;2===s&&(t=o[e.charCodeAt(h)]<<2|o[e.charCodeAt(h+1)]>>4,a[u++]=255&t);1===s&&(t=o[e.charCodeAt(h)]<<10|o[e.charCodeAt(h+1)]<<4|o[e.charCodeAt(h+2)]>>2,a[u++]=t>>8&255,a[u++]=255&t);return a},t.fromByteArray=function(e){for(var t,r=e.length,o=r%3,i=[],s=0,a=r-o;sa?a:s+16383));1===o?(t=e[r-1],i.push(n[t>>2]+n[t<<4&63]+"==")):2===o&&(t=(e[r-2]<<8)+e[r-1],i.push(n[t>>10]+n[t>>4&63]+n[t<<2&63]+"="));return i.join("")};for(var n=[],o=[],i="undefined"!=typeof Uint8Array?Uint8Array:Array,s="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",a=0,u=s.length;a0)throw new Error("Invalid string. Length must be a multiple of 4");var r=e.indexOf("=");return-1===r&&(r=t),[r,r===t?0:4-r%4]}function l(e,t,r){for(var o,i,s=[],a=t;a>18&63]+n[i>>12&63]+n[i>>6&63]+n[63&i]);return s.join("")}o["-".charCodeAt(0)]=62,o["_".charCodeAt(0)]=63},function(e,t){t.read=function(e,t,r,n,o){var i,s,a=8*o-n-1,u=(1<>1,l=-7,h=r?o-1:0,f=r?-1:1,d=e[t+h];for(h+=f,i=d&(1<<-l)-1,d>>=-l,l+=a;l>0;i=256*i+e[t+h],h+=f,l-=8);for(s=i&(1<<-l)-1,i>>=-l,l+=n;l>0;s=256*s+e[t+h],h+=f,l-=8);if(0===i)i=1-c;else{if(i===u)return s?NaN:1/0*(d?-1:1);s+=Math.pow(2,n),i-=c}return(d?-1:1)*s*Math.pow(2,i-n)},t.write=function(e,t,r,n,o,i){var s,a,u,c=8*i-o-1,l=(1<>1,f=23===o?Math.pow(2,-24)-Math.pow(2,-77):0,d=n?0:i-1,p=n?1:-1,m=t<0||0===t&&1/t<0?1:0;for(t=Math.abs(t),isNaN(t)||t===1/0?(a=isNaN(t)?1:0,s=l):(s=Math.floor(Math.log(t)/Math.LN2),t*(u=Math.pow(2,-s))<1&&(s--,u*=2),(t+=s+h>=1?f/u:f*Math.pow(2,1-h))*u>=2&&(s++,u/=2),s+h>=l?(a=0,s=l):s+h>=1?(a=(t*u-1)*Math.pow(2,o),s+=h):(a=t*Math.pow(2,h-1)*Math.pow(2,o),s=0));o>=8;e[r+d]=255&a,d+=p,a/=256,o-=8);for(s=s<0;e[r+d]=255&s,d+=p,s/=256,c-=8);e[r+d-p]|=128*m}},function(e,t){var r={}.toString;e.exports=Array.isArray||function(e){return"[object Array]"==r.call(e)}},function(e,t,r){var n,o,i;o=[],void 0===(i="function"==typeof(n=function(){var e,t,r,n;Object.keys||(Object.keys=(e=Object.prototype.hasOwnProperty,t=!{toString:null}.propertyIsEnumerable("toString"),n=(r=["toString","toLocaleString","valueOf","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","constructor"]).length,function(o){if("object"!=typeof o&&"function"!=typeof o||null===o)throw new TypeError("Object.keys called on non-object");var i=[];for(var s in o)e.call(o,s)&&i.push(s);if(t)for(var a=0;a>>0;if(0===r)return-1;var n=0;if(arguments.length>1&&((n=Number(arguments[1]))!=n?n=0:0!==n&&n!==1/0&&n!==-1/0&&(n=(n>0||-1)*Math.floor(Math.abs(n)))),n>=r)return-1;for(var o=n>=0?n:Math.max(r-Math.abs(n),0);o0&&(t+=l.suffices["*"]&&r||",",l.suffices["*"]&&u&&(t+=l.name+"=")),t+=a?encodeURIComponent(h[d]).replace(/!/g,"%21"):s(h[d])}else if("object"==typeof h){u&&!l.suffices["*"]&&(t+=l.name+"=");var p=!0;for(var m in h)p||(t+=l.suffices["*"]&&r||","),p=!1,t+=a?encodeURIComponent(m).replace(/!/g,"%21"):s(m),t+=l.suffices["*"]?"=":",",t+=a?encodeURIComponent(h[m]).replace(/!/g,"%21"):s(h[m])}else u&&(t+=l.name,c&&""===h||(t+="=")),null!=l.truncate&&(h=h.substring(0,l.truncate)),t+=a?encodeURIComponent(h).replace(/!/g,"%21"):s(h)}return t};return _.varNames=l,{prefix:n,substitution:_}}function u(e){if(!(this instanceof u))return new u(e);for(var t=e.split("{"),r=[t.shift()],n=[],o=[],i=[];t.length>0;){var s=t.shift(),c=s.split("}")[0],l=s.substring(c.length+1),h=a(c);o.push(h.substitution),n.push(h.prefix),r.push(l),i=i.concat(h.substitution.varNames)}this.fill=function(e){for(var t=r[0],n=0;n0&&"/"===t.charAt(e.length-1)||"#"===r.charAt(0)||"?"===r.charAt(0))return!0}return!1}(t,e.id)&&void 0===this.schemas[e.id]&&(this.schemas[e.id]=e),e)if("enum"!==n)if("object"==typeof e[n])this.searchSchemas(e[n],t);else if("$ref"===n){var o=m(e[n]);o&&void 0===this.schemas[o]&&void 0===this.missingMap[o]&&(this.missingMap[o]=o)}},c.prototype.addSchema=function(e,t){if("string"!=typeof e||void 0===t){if("object"!=typeof e||"string"!=typeof e.id)return;e=(t=e).id}e===m(e)+"#"&&(e=m(e)),this.schemas[e]=t,delete this.missingMap[e],y(t,e),this.searchSchemas(t,e)},c.prototype.getSchemaMap=function(){var e={};for(var t in this.schemas)e[t]=this.schemas[t];return e},c.prototype.getSchemaUris=function(e){var t=[];for(var r in this.schemas)e&&!e.test(r)||t.push(r);return t},c.prototype.getMissingUris=function(e){var t=[];for(var r in this.missingMap)e&&!e.test(r)||t.push(r);return t},c.prototype.dropSchemas=function(){this.schemas={},this.reset()},c.prototype.reset=function(){this.missing=[],this.missingMap={},this.errors=[]},c.prototype.validateAll=function(e,t,r,n,o){var i;if(!(t=this.resolveRefs(t)))return null;if(t instanceof P)return this.errors.push(t),t;var s,a=this.errors.length,u=null,c=null;if(this.checkRecursive&&e&&"object"==typeof e){if(i=!this.scanned.length,e[this.validatedSchemasKey]){var l=e[this.validatedSchemasKey].indexOf(t);if(-1!==l)return this.errors=this.errors.concat(e[this.validationErrorsKey][l]),null}if(Object.isFrozen(e)&&-1!==(s=this.scannedFrozen.indexOf(e))){var h=this.scannedFrozenSchemas[s].indexOf(t);if(-1!==h)return this.errors=this.errors.concat(this.scannedFrozenValidationErrors[s][h]),null}if(this.scanned.push(e),Object.isFrozen(e))-1===s&&(s=this.scannedFrozen.length,this.scannedFrozen.push(e),this.scannedFrozenSchemas.push([])),u=this.scannedFrozenSchemas[s].length,this.scannedFrozenSchemas[s][u]=t,this.scannedFrozenValidationErrors[s][u]=[];else{if(!e[this.validatedSchemasKey])try{Object.defineProperty(e,this.validatedSchemasKey,{value:[],configurable:!0}),Object.defineProperty(e,this.validationErrorsKey,{value:[],configurable:!0})}catch(t){e[this.validatedSchemasKey]=[],e[this.validationErrorsKey]=[]}c=e[this.validatedSchemasKey].length,e[this.validatedSchemasKey][c]=t,e[this.validationErrorsKey][c]=[]}}var f=this.errors.length,d=this.validateBasic(e,t,o)||this.validateNumeric(e,t,o)||this.validateString(e,t,o)||this.validateArray(e,t,o)||this.validateObject(e,t,o)||this.validateCombinations(e,t,o)||this.validateHypermedia(e,t,o)||this.validateFormat(e,t,o)||this.validateDefinedKeywords(e,t,o)||null;if(i){for(;this.scanned.length;){var p=this.scanned.pop();delete p[this.validatedSchemasKey]}this.scannedFrozen=[],this.scannedFrozenSchemas=[]}if(d||f!==this.errors.length)for(;r&&r.length||n&&n.length;){var m=r&&r.length?""+r.pop():null,y=n&&n.length?""+n.pop():null;d&&(d=d.prefixWith(m,y)),this.prefixErrors(f,m,y)}return null!==u?this.scannedFrozenValidationErrors[s][u]=this.errors.slice(a):null!==c&&(e[this.validationErrorsKey][c]=this.errors.slice(a)),this.handleError(d)},c.prototype.validateFormat=function(e,t){if("string"!=typeof t.format||!this.formatValidators[t.format])return null;var r=this.formatValidators[t.format].call(null,e,t);return"string"==typeof r||"number"==typeof r?this.createError(v.FORMAT_CUSTOM,{message:r},"","/format",null,e,t):r&&"object"==typeof r?this.createError(v.FORMAT_CUSTOM,{message:r.message||"?"},r.dataPath||"",r.schemaPath||"/format",null,e,t):null},c.prototype.validateDefinedKeywords=function(e,t,r){for(var n in this.definedKeywords)if(void 0!==t[n])for(var o=this.definedKeywords[n],i=0;i=h&&nt.maximum)return this.createError(v.NUMBER_MAXIMUM,{value:e,maximum:t.maximum},"","/maximum",null,e,t);if(t.exclusiveMaximum&&e===t.maximum)return this.createError(v.NUMBER_MAXIMUM_EXCLUSIVE,{value:e,maximum:t.maximum},"","/exclusiveMaximum",null,e,t)}return null},c.prototype.validateNaN=function(e,t){return"number"!=typeof e?null:!0===isNaN(e)||e===1/0||e===-1/0?this.createError(v.NUMBER_NOT_A_NUMBER,{value:e},"","/type",null,e,t):null},c.prototype.validateString=function(e,t,r){return this.validateStringLength(e,t,r)||this.validateStringPattern(e,t,r)||null},c.prototype.validateStringLength=function(e,t){return"string"!=typeof e?null:void 0!==t.minLength&&e.lengtht.maxLength?this.createError(v.STRING_LENGTH_LONG,{length:e.length,maximum:t.maxLength},"","/maxLength",null,e,t):null},c.prototype.validateStringPattern=function(e,t){if("string"!=typeof e||"string"!=typeof t.pattern&&!(t.pattern instanceof RegExp))return null;var r;if(t.pattern instanceof RegExp)r=t.pattern;else{var n,o="",i=t.pattern.match(/^\/(.+)\/([img]*)$/);i?(n=i[1],o=i[2]):n=t.pattern,r=new RegExp(n,o)}return r.test(e)?null:this.createError(v.STRING_PATTERN,{pattern:t.pattern},"","/pattern",null,e,t)},c.prototype.validateArray=function(e,t,r){return Array.isArray(e)&&(this.validateArrayLength(e,t,r)||this.validateArrayUniqueItems(e,t,r)||this.validateArrayItems(e,t,r))||null},c.prototype.validateArrayLength=function(e,t){var r;return void 0!==t.minItems&&e.lengtht.maxItems&&(r=this.createError(v.ARRAY_LENGTH_LONG,{length:e.length,maximum:t.maxItems},"","/maxItems",null,e,t),this.handleError(r))?r:null},c.prototype.validateArrayUniqueItems=function(e,t){if(t.uniqueItems)for(var r=0;rt.maxProperties&&(r=this.createError(v.OBJECT_PROPERTIES_MAXIMUM,{propertyCount:n.length,maximum:t.maxProperties},"","/maxProperties",null,e,t),this.handleError(r))?r:null},c.prototype.validateObjectRequiredProperties=function(e,t){if(void 0!==t.required)for(var r=0;r 10000");if(void 0!==v[e])throw new Error("Error already defined: "+e+" as "+v[e]);if(void 0!==b[t])throw new Error("Error code already used: "+b[t]+" as "+t);for(var n in v[e]=t,b[t]=e,w[e]=w[t]=r,E){var o=E[n];o[e]&&(o[t]=o[t]||o[e])}},reset:function(){o.reset(),this.error=null,this.missing=[],this.valid=!0},missing:[],error:null,valid:!0,normSchema:y,resolveUrl:p,getDocumentUri:m,errorCodes:v};return i.language(t||"en"),i}();return S.addLanguage("en-gb",w),S.tv4=S,S})?n.apply(t,o):n)||(e.exports=i)},function(e,t){var r={uris:{},schemas:{},aliases:{},declare:function(e,t,r,n){var o=e+"/"+t;if(n.extends){var i,s=n.extends.split("/");i=1===s.length?e+"/"+s.shift():s.join("/");var a=this.uris[i];if(!a)throw"Type '"+o+"' tries to extend unknown schema '"+i+"'";n.extends=this.schemas[a]}this.uris[o]=r,this.aliases[r]=o,this.schemas[r]=n},resolveAlias:function(e){return this.uris[e]},getSchema:function(e){return this.schemas[e]},inScope:function(e){var t=e.length,r={};for(var n in this.uris)if(n.substr(0,t+1)===e+"/"){var o=this.uris[n];r[o]=this.schemas[o]}return r}},n=function(e){var t=new Error("Schema not found: "+e);return t.name="SchemaNotFound",t};n.prototype=Error.prototype,r.SchemaNotFound=n,e.exports=r},function(e,t){function r(e){this.defaultValue=e,this._canPropagate=!1,this._storage={},this._itemsRev={},this.activatePropagation()}r.prototype={get:function(e){e=e.toLowerCase();var t=this._storage[e];return void 0===t&&(t=this.defaultValue,this._storage[e]=t),t},set:function(e,t){return e=e.toLowerCase(),this._storage[e]===t?t:(this._storage[e]=t,t||delete this._itemsRev[e],this._updateParentFolderItemRev(e,t),this._canPropagate&&this._propagate(e),t)},delete:function(e){return this.set(e,null)},deactivatePropagation:function(){return this._canPropagate=!1,!0},activatePropagation:function(){return!!this._canPropagate||(this._generateFolderRev("/"),this._canPropagate=!0,!0)},_hashCode:function(e){var t,r=0;if(0===e.length)return r;for(t=0;t0&&(r=this._generateHash(n))}return this.set(e,r),r}},e.exports=r},function(e,t,r){var n; +/*! + * webfinger.js + * version 2.7.0 + * http://github.com/silverbucket/webfinger.js + * + * Developed and Maintained by: + * Nick Jennings 2012 + * + * webfinger.js is released under the AGPL (see LICENSE). + * + * You don't have to do anything special to choose one license or the other and you don't + * have to notify anyone which license you are using. + * Please see the corresponding license file for details of these licenses. + * You are free to use, modify and distribute this software, but all copyright + * information must remain. + * + */"function"!=typeof fetch&&"function"!=typeof XMLHttpRequest&&(XMLHttpRequest=r(26)),function(r){var o={"http://webfist.org/spec/rel":"webfist","http://webfinger.net/rel/avatar":"avatar",remotestorage:"remotestorage","http://tools.ietf.org/id/draft-dejong-remotestorage":"remotestorage",remoteStorage:"remotestorage","http://www.packetizer.com/rel/share":"share","http://webfinger.net/rel/profile-page":"profile",me:"profile",vcard:"vcard",blog:"blog","http://packetizer.com/rel/blog":"blog","http://schemas.google.com/g/2010#updates-from":"updates","https://camlistore.org/rel/server":"camilstore"},i={avatar:[],remotestorage:[],blog:[],vcard:[],updates:[],share:[],profile:[],webfist:[],camlistore:[]},s=["webfinger","host-meta","host-meta.json"];function a(e){return e.toString=function(){return this.message},e}function u(e){"object"!=typeof e&&(e={}),this.config={tls_only:void 0===e.tls_only||e.tls_only,webfist_fallback:void 0!==e.webfist_fallback&&e.webfist_fallback,uri_fallback:void 0!==e.uri_fallback&&e.uri_fallback,request_timeout:void 0!==e.request_timeout?e.request_timeout:1e4}}u.prototype.__fetchJRD=function(e,t,r){if("function"==typeof fetch)return this.__fetchJRD_fetch(e,t,r);if("function"==typeof XMLHttpRequest)return this.__fetchJRD_XHR(e,t,r);throw new Error("add a polyfill for fetch or XMLHttpRequest")},u.prototype.__fetchJRD_fetch=function(e,t,r){var n,o=this;"function"==typeof AbortController&&(n=new AbortController);var i=fetch(e,{headers:{Accept:"application/jrd+json, application/json"},signal:n?n.signal:void 0}).then(function(t){if(t.ok)return t.text();throw 404===t.status?a({message:"resource not found",url:e,status:t.status}):a({message:"error during request",url:e,status:t.status})},function(t){throw a({message:"error during request",url:e,status:void 0,err:t})}).then(function(t){if(o.__isValidJSON(t))return t;throw a({message:"invalid json",url:e,status:void 0})}),s=new Promise(function(t,r){setTimeout(function(){r(a({message:"request timed out",url:e,status:void 0})),n&&n.abort()},o.config.request_timeout)});Promise.race([i,s]).then(function(e){r(e)}).catch(function(e){t(e)})},u.prototype.__fetchJRD_XHR=function(e,t,r){var n=this,o=!1,i=new XMLHttpRequest;function s(){if(!o){if(o=!0,200===i.status)return n.__isValidJSON(i.responseText)?r(i.responseText):t(a({message:"invalid json",url:e,status:i.status}));if(404===i.status)return t(a({message:"resource not found",url:e,status:i.status}));if(i.status>=301&&i.status<=302){var s=i.getResponseHeader("Location");return function(e){return"string"==typeof e&&"https"===e.split("://")[0]}(s)?u():t(a({message:"no redirect URL found",url:e,status:i.status}))}return t(a({message:"error during request",url:e,status:i.status}))}}function u(){i.onreadystatechange=function(){4===i.readyState&&s()},i.onload=function(){s()},i.ontimeout=function(){return t(a({message:"request timed out",url:e,status:i.status}))},i.open("GET",e,!0),i.timeout=n.config.request_timeout,i.setRequestHeader("Accept","application/jrd+json, application/json"),i.send()}return u()},u.prototype.__isValidJSON=function(e){try{JSON.parse(e)}catch(e){return!1}return!0},u.prototype.__isLocalhost=function(e){return/^localhost(\.localdomain)?(\:[0-9]+)?$/.test(e)},u.prototype.__processJRD=function(e,t,r,n){var s=JSON.parse(t);if("object"!=typeof s||"object"!=typeof s.links)return void 0!==s.error?r(a({message:s.error,request:e})):r(a({message:"unknown response from server",request:e}));var u=s.links;Array.isArray(u)||(u=[]);var c={object:s,json:t,idx:{}};c.idx.properties={name:void 0},c.idx.links=JSON.parse(JSON.stringify(i)),u.map(function(e,t){if(o.hasOwnProperty(e.rel)&&c.idx.links[o[e.rel]]){var r={};Object.keys(e).map(function(t,n){r[t]=e[t]}),c.idx.links[o[e.rel]].push(r)}});var l=JSON.parse(t).properties;for(var h in l)l.hasOwnProperty(h)&&"http://packetizer.com/ns/name"===h&&(c.idx.properties.name=l[h]);return n(c)},u.prototype.lookup=function(e,t){if("string"!=typeof e)throw new Error("first parameter must be a user address");if("function"!=typeof t)throw new Error("second parameter must be a callback");var r=this,n="";n=e.indexOf("://")>-1?e.replace(/ /g,"").split("/")[2]:e.replace(/ /g,"").split("@")[1];var o=0,i="https";function a(){var t="";return e.split("://")[1]||(t="acct:"),i+"://"+n+"/.well-known/"+s[o]+"?resource="+t+e}function u(e){if(r.config.uri_fallback&&"webfist.org"!==n&&o!==s.length-1)return o+=1,c();if(!r.config.tls_only&&"https"===i)return o=0,i="http",c();if(!r.config.webfist_fallback||"webfist.org"===n)return t(e);o=0,i="http",n="webfist.org";var u=a();r.__fetchJRD(u,t,function(e){r.__processJRD(u,e,t,function(e){"object"==typeof e.idx.links.webfist&&"string"==typeof e.idx.links.webfist[0].href&&r.__fetchJRD(e.idx.links.webfist[0].href,t,function(e){r.__processJRD(u,e,t,function(e){return t(null,t)})})})})}function c(){var e=a();r.__fetchJRD(e,u,function(n){r.__processJRD(e,n,t,function(e){t(null,e)})})}return r.__isLocalhost(n)&&(i="http"),setTimeout(c,0)},u.prototype.lookupLink=function(e,t,r){if(!i.hasOwnProperty(t))return r("unsupported rel "+t);this.lookup(e,function(e,n){var o=n.idx.links[t];return e?r(e):0===o.length?r('no links found with rel="'+t+'"'):r(null,o[0])})},void 0===(n=function(){return u}.apply(t,[]))||(e.exports=n)}()},function(e,t){e.exports=XMLHttpRequest},function(e,t,r){"use strict";function n(e){return(n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}var o=r(0),i=r(1),s=r(28),a=r(3),u={features:[],featuresDone:0,readyFired:!1,loadFeatures:function(){var e=this;for(var t in this.features=[],this.featuresDone=0,this.readyFired=!1,this.featureModules={WireClient:r(6),Dropbox:r(11),GoogleDrive:r(13),Access:r(15),Discover:r(14),Authorize:r(4),BaseClient:r(5),Env:r(12)},a.cache&&o.extend(this.featureModules,{Caching:r(16),IndexedDB:r(29),LocalStorage:r(30),InMemoryStorage:r(31),Sync:r(7)}),a.disableFeatures.forEach(function(t){e.featureModules[t]&&delete e.featureModules[t]}),this._allLoaded=!1,this.featureModules)this.loadFeature(t)},hasFeature:function(e){for(var t=this.features.length-1;t>=0;t--)if(this.features[t].name===e)return this.features[t].supported;return!1},loadFeature:function(e){var t=this,r=this.featureModules[e],o=!r._rs_supported||r._rs_supported();i("[RemoteStorage] [FEATURE ".concat(e,"] initializing ...")),"object"===n(o)?o.then(function(){t.featureSupported(e,!0),t.initFeature(e)},function(){t.featureSupported(e,!1)}):"boolean"==typeof o?(this.featureSupported(e,o),o&&this.initFeature(e)):this.featureSupported(e,!1)},initFeature:function(e){var t,r=this,o=this.featureModules[e];try{t=o._rs_init(this)}catch(t){return void this.featureFailed(e,t)}"object"===n(t)&&"function"==typeof t.then?t.then(function(){r.featureInitialized(e)},function(t){r.featureFailed(e,t)}):this.featureInitialized(e)},featureFailed:function(e,t){i("[RemoteStorage] [FEATURE ".concat(e,"] initialization failed (").concat(t,")")),this.featureDone()},featureSupported:function(e,t){i("[RemoteStorage] [FEATURE ".concat(e,"] ").concat(t?"":" not"," supported")),t||this.featureDone()},featureInitialized:function(e){i("[RemoteStorage] [FEATURE ".concat(e,"] initialized.")),this.features.push({name:e,init:this.featureModules[e]._rs_init,supported:!0,cleanup:this.featureModules[e]._rs_cleanup}),this.featureDone()},featureDone:function(){this.featuresDone++,this.featuresDone===Object.keys(this.featureModules).length&&setTimeout(this.featuresLoaded.bind(this),0)},_setCachingModule:function(){var e=this;["IndexedDB","LocalStorage","InMemoryStorage"].some(function(t){if(e.features.some(function(e){return e.name===t}))return e.features.local=e.featureModules[t],!0})},_fireReady:function(){try{this.readyFired||(this._emit("ready"),this.readyFired=!0)}catch(e){console.error("'ready' failed: ",e,e.stack),this._emit("error",e)}},featuresLoaded:function(){var e=this;i("[REMOTESTORAGE] All features loaded !"),this._setCachingModule(),this.local=a.cache&&this.features.local&&new this.features.local,this.local&&this.remote?(this._setGPD(s,this),this._bindChange(this.local)):this.remote&&this._setGPD(this.remote,this.remote),this.remote&&(this.remote.on("connected",function(){e._fireReady(),e._emit("connected")}),this.remote.on("not-connected",function(){e._fireReady(),e._emit("not-connected")}),this.remote.connected&&(this._fireReady(),this._emit("connected")),this.hasFeature("Authorize")||this.remote.stopWaitingForToken()),this._collectCleanupFunctions();try{this._allLoaded=!0,this._emit("features-loaded")}catch(e){o.logError(e),this._emit("error",e)}this._processPending()},_collectCleanupFunctions:function(){this._cleanups=[];for(var e=0;e0?this.getNodesFromDb(t).then(function(e){for(var t in r)e[t]=r[t];return e}):Promise.resolve(r)},setNodes:function(e){for(var t in e)this.changesQueued[t]=e[t]||!1;return this.maybeFlush(),Promise.resolve()},maybeFlush:function(){0===this.putsRunning?this.flushChangesQueued():this.commitSlownessWarning||(this.commitSlownessWarning=setInterval(function(){console.warn("WARNING: waited more than 10 seconds for previous commit to finish")},1e4))},flushChangesQueued:function(){this.commitSlownessWarning&&(clearInterval(this.commitSlownessWarning),this.commitSlownessWarning=null),Object.keys(this.changesQueued).length>0&&(this.changesRunning=this.changesQueued,this.changesQueued={},this.setNodesInDb(this.changesRunning).then(this.flushChangesQueued.bind(this)))},getNodesFromDb:function(e){var t=this;return new Promise(function(r,n){var o=t.db.transaction(["nodes"],"readonly"),i=o.objectStore("nodes"),s={};t.getsRunning++,e.map(function(e){i.get(e).onsuccess=function(t){s[e]=t.target.result}}),o.oncomplete=function(){r(s),this.getsRunning--}.bind(t),o.onerror=o.onabort=function(){n("get transaction error/abort"),this.getsRunning--}.bind(t)})},setNodesInDb:function(e){var t=this;return new Promise(function(r,o){var s=t.db.transaction(["nodes"],"readwrite"),a=s.objectStore("nodes"),u=(new Date).getTime();for(var c in t.putsRunning++,i("[IndexedDB] Starting put",e,t.putsRunning),e){var l=e[c];if("object"===n(l))try{a.put(l)}catch(e){throw i("[IndexedDB] Error while putting",l,e),e}else try{a.delete(c)}catch(e){throw i("[IndexedDB] Error while removing",a,l,e),e}}s.oncomplete=function(){this.putsRunning--,i("[IndexedDB] Finished put",e,this.putsRunning,(new Date).getTime()-u+"ms"),r()}.bind(t),s.onerror=function(){this.putsRunning--,o("transaction error")}.bind(t),s.onabort=function(){o("transaction abort"),this.putsRunning--}.bind(t)})},reset:function(e){var t=this,r=this.db.name;this.db.close(),c.clean(this.db.name,function(){c.open(r,function(r,n){r?i("[IndexedDB] Error while resetting local storage",r):t.db=n,"function"==typeof e&&e(self)})})},forAllNodes:function(e){var t=this;return new Promise(function(r){t.db.transaction(["nodes"],"readonly").objectStore("nodes").openCursor().onsuccess=function(n){var o=n.target.result;o?(e(t.migrate(o.value)),o.continue()):r()}})},closeDB:function(){0===this.putsRunning?this.db.close():setTimeout(this.closeDB.bind(this),100)}},c.open=function(e,t){var r=setTimeout(function(){t("timeout trying to open db")},1e4);try{var n=indexedDB.open(e,2);n.onerror=function(){i("[IndexedDB] Opening DB failed",n),clearTimeout(r),t(n.error)},n.onupgradeneeded=function(e){var t=n.result;i("[IndexedDB] Upgrade: from ",e.oldVersion," to ",e.newVersion),1!==e.oldVersion&&(i("[IndexedDB] Creating object store: nodes"),t.createObjectStore("nodes",{keyPath:"path"})),i("[IndexedDB] Creating object store: changes"),t.createObjectStore("changes",{keyPath:"path"})},n.onsuccess=function(){clearTimeout(r);var o=n.result;if(!o.objectStoreNames.contains("nodes")||!o.objectStoreNames.contains("changes"))return i("[IndexedDB] Missing object store. Resetting the database."),void c.clean(e,function(){c.open(e,t)});t(null,n.result)}}catch(n){i("[IndexedDB] Failed to open database: "+n),i("[IndexedDB] Resetting database and trying again."),clearTimeout(r),c.clean(e,function(){c.open(e,t)})}},c.clean=function(e,t){var r=indexedDB.deleteDatabase(e);r.onsuccess=function(){i("[IndexedDB] Done removing DB"),t()},r.onerror=r.onabort=function(t){console.error('Failed to remove database "'+e+'"',t)}},c._rs_init=function(e){return new Promise(function(t,r){c.open("remotestorage",function(n,i){n?r(n):(o=i,i.onerror=function(){e._emit("error",n)},t())})})},c._rs_supported=function(){return new Promise(function(e,t){var r=u.getGlobalContext(),n=!1;if("undefined"!=typeof navigator&&navigator.userAgent.match(/Android (2|3|4\.[0-3])/)&&(navigator.userAgent.match(/Chrome|Firefox/)||(n=!0)),"indexedDB"in r&&!n)try{var o=indexedDB.open("rs-check");o.onerror=function(){t()},o.onsuccess=function(){o.result.close(),indexedDB.deleteDatabase("rs-check"),e()}}catch(e){t()}else t()})},c._rs_cleanup=function(e){return new Promise(function(t){e.local&&e.local.closeDB(),c.clean("remotestorage",t)})},e.exports=c},function(e,t,r){var n=r(8),o=r(1),i=r(2),s=r(0),a="remotestorage:cache:nodes:",u="remotestorage:cache:changes:",c=function(){n(this),o("[LocalStorage] Registering events"),i(this,"change","local-events-done")};function l(e){return e.substr(0,a.length)===a||e.substr(0,u.length)===u}c.prototype={getNodes:function(e){for(var t={},r=0,n=e.length;r= 0) {\n // Circular reference, don't attempt to compare this object.\n // If nothing else returns false, the objects match.\n continue;\n }\n\n seenArg = seen.slice();\n seenArg.push(b[key]);\n }\n\n if (!util.equal(a[key], b[key], seenArg)) {\n return false;\n }\n }\n }\n\n return true;\n },\n\n deepClone (obj) {\n var clone;\n if (obj === undefined) {\n return undefined;\n } else {\n clone = JSON.parse(JSON.stringify(obj));\n fixArrayBuffers(obj, clone);\n return clone;\n }\n },\n\n pathsFromRoot (path) {\n var paths = [path];\n var parts = path.replace(/\\/$/, '').split('/');\n\n while (parts.length > 1) {\n parts.pop();\n paths.push(parts.join('/')+'/');\n }\n return paths;\n },\n\n localStorageAvailable () {\n const context = util.getGlobalContext();\n\n if (!('localStorage' in context)) { return false; }\n\n try {\n context.localStorage.setItem('rs-check', 1);\n context.localStorage.removeItem('rs-check');\n return true;\n } catch(error) {\n return false;\n }\n },\n\n /**\n * Extract and parse JSON data from localStorage.\n *\n * @param {string} key - localStorage key\n *\n * @returns {object} parsed object or undefined\n */\n getJSONFromLocalStorage (key) {\n const context = util.getGlobalContext();\n\n try {\n return JSON.parse(context.localStorage.getItem(key));\n } catch(e) {\n // no JSON stored\n }\n },\n\n /**\n * Decide if data should be treated as binary based on the content (presence of non-printable characters\n * or replacement character) and content-type.\n *\n * @param {string} content - The data\n * @param {string} mimeType - The data's content-type\n *\n * @returns {boolean}\n */\n shouldBeTreatedAsBinary (content, mimeType) {\n // eslint-disable-next-line no-control-regex\n return (mimeType && mimeType.match(/charset=binary/)) || /[\\x00-\\x08\\x0E-\\x1F\\uFFFD]/.test(content);\n },\n\n /**\n * Read data from an ArrayBuffer and return it as a string\n * @param {ArrayBuffer} arrayBuffer \n * @param {string} encoding \n * @returns {Promise} Resolves with a string containing the data\n */\n getTextFromArrayBuffer(arrayBuffer, encoding) {\n return new Promise((resolve/*, reject*/) => {\n if (typeof Blob === 'undefined') {\n var buffer = new Buffer(new Uint8Array(arrayBuffer));\n resolve(buffer.toString(encoding));\n } else {\n var blob;\n util.globalContext.BlobBuilder = util.globalContext.BlobBuilder || util.globalContext.WebKitBlobBuilder;\n if (typeof util.globalContext.BlobBuilder !== 'undefined') {\n var bb = new global.BlobBuilder();\n bb.append(arrayBuffer);\n blob = bb.getBlob();\n } else {\n blob = new Blob([arrayBuffer]);\n }\n \n var fileReader = new FileReader();\n if (typeof fileReader.addEventListener === 'function') {\n fileReader.addEventListener('loadend', function (evt) {\n resolve(evt.target.result);\n });\n } else {\n fileReader.onloadend = function(evt) {\n resolve(evt.target.result);\n };\n }\n fileReader.readAsText(blob, encoding);\n }\n });\n }\n \n\n};\n\nmodule.exports = util;\n","var config = require('./config');\n\n/**\n * Log using console.log, when remoteStorage logging is enabled.\n *\n * You can enable logging with ``RemoteStorage#enableLog``.\n *\n * (In node.js you can also enable logging during remoteStorage object\n * creation. See: {@link RemoteStorage}).\n */\nfunction log() {\n if (config.logging) {\n // eslint-disable-next-line no-console\n console.log.apply(console, arguments);\n }\n}\n\nmodule.exports = log;\n","var log = require('./log');\n\n/**\n * @interface\n */\nvar methods = {\n /**\n * Install an event handler for the given event name\n */\n addEventListener: function (eventName, handler) {\n if (typeof(eventName) !== 'string') {\n throw new Error('Argument eventName should be a string');\n }\n if (typeof(handler) !== 'function') {\n throw new Error('Argument handler should be a function');\n }\n log('[Eventhandling] Adding event listener', eventName);\n this._validateEvent(eventName);\n this._handlers[eventName].push(handler);\n },\n\n /**\n * Remove a previously installed event handler\n */\n removeEventListener: function (eventName, handler) {\n this._validateEvent(eventName);\n var hl = this._handlers[eventName].length;\n for (var i=0;i 0) {\n params['state'] = stateValue;\n }\n } else {\n params[decodeURIComponent(kv[0])] = decodeURIComponent(kv[1]);\n }\n\n return params;\n }, {});\n}\n\nfunction buildOAuthURL (authURL, redirectUri, scope, clientId) {\n const hashPos = redirectUri.indexOf('#');\n let url = authURL;\n\n url += authURL.indexOf('?') > 0 ? '&' : '?';\n url += 'redirect_uri=' + encodeURIComponent(redirectUri.replace(/#.*$/, ''));\n url += '&scope=' + encodeURIComponent(scope);\n url += '&client_id=' + encodeURIComponent(clientId);\n\n if (hashPos !== - 1 && hashPos+1 !== redirectUri.length) {\n url += '&state=' + encodeURIComponent(redirectUri.substring(hashPos+1));\n }\n url += '&response_type=token';\n\n return url;\n}\n\nconst Authorize = function (remoteStorage, { authURL, scope, redirectUri, clientId }) {\n log('[Authorize] authURL = ', authURL, 'scope = ', scope, 'redirectUri = ', redirectUri, 'clientId = ', clientId);\n\n // keep track of the discovery data during redirect if we can't save it in localStorage\n if (!util.localStorageAvailable() &&\n remoteStorage.backend === 'remotestorage') {\n redirectUri += redirectUri.indexOf('#') > 0 ? '&' : '#';\n\n var discoveryData = {\n userAddress: remoteStorage.remote.userAddress,\n href: remoteStorage.remote.href,\n storageApi: remoteStorage.remote.storageApi,\n properties: remoteStorage.remote.properties\n };\n\n redirectUri += 'rsDiscovery=' + btoa(JSON.stringify(discoveryData));\n }\n\n const url = buildOAuthURL(authURL, redirectUri, scope, clientId);\n\n if (util.globalContext.cordova) {\n return Authorize.openWindow(url, redirectUri, 'location=yes,clearsessioncache=yes,clearcache=yes')\n .then(function(authResult) {\n remoteStorage.remote.configure({\n token: authResult.access_token\n });\n });\n }\n\n Authorize.setLocation(url);\n};\n\nAuthorize.IMPLIED_FAKE_TOKEN = false;\n\nAuthorize.Unauthorized = function(message, options = {}) {\n this.name = 'Unauthorized';\n\n if (typeof message === 'undefined') {\n this.message = 'App authorization expired or revoked.';\n } else {\n this.message = message;\n }\n\n if (typeof options.code !== 'undefined') {\n this.code = options.code;\n }\n\n this.stack = (new Error()).stack;\n};\nAuthorize.Unauthorized.prototype = Object.create(Error.prototype);\nAuthorize.Unauthorized.prototype.constructor = Authorize.Unauthorized;\n\n/**\n * Get current document location\n *\n * Override this method if access to document.location is forbidden\n */\nAuthorize.getLocation = function () {\n return document.location;\n};\n\n/**\n * Set current document location\n *\n * Override this method if access to document.location is forbidden\n */\nAuthorize.setLocation = function (location) {\n if (typeof location === 'string') {\n document.location.href = location;\n } else if (typeof location === 'object') {\n document.location = location;\n } else {\n throw \"Invalid location \" + location;\n }\n};\n\n/**\n * Open new InAppBrowser window for OAuth in Cordova\n */\nAuthorize.openWindow = function (url, redirectUri, options) {\n return new Promise( (resolve, reject) => {\n\n var newWindow = open(url, '_blank', options);\n\n if (!newWindow || newWindow.closed) {\n return reject('Authorization popup was blocked');\n }\n\n var handleExit = function () {\n return reject('Authorization was canceled');\n };\n\n var handleLoadstart = function (event) {\n if (event.url.indexOf(redirectUri) !== 0) {\n return;\n }\n\n newWindow.removeEventListener('exit', handleExit);\n newWindow.close();\n\n var authResult = extractParams(event.url);\n\n if (!authResult) {\n return reject('Authorization error');\n }\n\n return resolve(authResult);\n };\n\n newWindow.addEventListener('loadstart', handleLoadstart);\n newWindow.addEventListener('exit', handleExit);\n\n });\n};\n\n\nAuthorize._rs_supported = function () {\n return typeof(document) !== 'undefined';\n};\n\nvar onFeaturesLoaded;\nAuthorize._rs_init = function (remoteStorage) {\n\n onFeaturesLoaded = function () {\n var authParamsUsed = false;\n if (params) {\n if (params.error) {\n if (params.error === 'access_denied') {\n throw new Authorize.Unauthorized('Authorization failed: access denied', { code: 'access_denied' });\n } else {\n throw new Authorize.Unauthorized(`Authorization failed: ${params.error}`);\n }\n }\n\n // rsDiscovery came with the redirect, because it couldn't be\n // saved in localStorage\n if (params.rsDiscovery) {\n remoteStorage.remote.configure(params.rsDiscovery);\n }\n\n if (params.access_token) {\n remoteStorage.remote.configure({\n token: params.access_token\n });\n authParamsUsed = true;\n }\n if (params.remotestorage) {\n remoteStorage.connect(params.remotestorage);\n authParamsUsed = true;\n }\n if (params.state) {\n location = Authorize.getLocation();\n Authorize.setLocation(location.href.split('#')[0]+'#'+params.state);\n }\n }\n if (!authParamsUsed) {\n remoteStorage.remote.stopWaitingForToken();\n }\n };\n var params = extractParams(),\n location;\n if (params) {\n location = Authorize.getLocation();\n location.hash = '';\n }\n remoteStorage.on('features-loaded', onFeaturesLoaded);\n};\n\nAuthorize._rs_cleanup = function (remoteStorage) {\n remoteStorage.removeEventListener('features-loaded', onFeaturesLoaded);\n};\n\nmodule.exports = Authorize;\n","const eventHandling = require('./eventhandling');\nconst util = require('./util');\nconst config = require('./config');\nconst tv4 = require('tv4');\nconst Types = require('./types');\n\nconst SchemaNotFound = Types.SchemaNotFound;\n\n/**\n * Provides a high-level interface to access data below a given root path.\n */\nvar BaseClient = function (storage, base) {\n if (base[base.length - 1] !== '/') {\n throw \"Not a folder: \" + base;\n }\n\n if (base === '/') {\n // allow absolute and relative paths for the root scope.\n this.makePath = function (path) {\n return (path[0] === '/' ? '' : '/') + path;\n };\n }\n\n /**\n * The instance this operates on.\n */\n this.storage = storage;\n\n /**\n * Base path, which this operates on.\n *\n * For the module's privateClient this would be //, for the\n * corresponding publicClient /public//.\n */\n this.base = base;\n\n /**\n * TODO: document what this does exactly\n */\n var parts = this.base.split('/');\n if (parts.length > 2) {\n this.moduleName = parts[1];\n } else {\n this.moduleName = 'root';\n }\n\n eventHandling(this, 'change');\n this.on = this.on.bind(this);\n storage.onChange(this.base, this._fireChange.bind(this));\n};\n\nBaseClient.Types = Types;\n\nBaseClient.prototype = {\n\n /**\n * Instantiate a new client, scoped to a subpath of the current client's\n * path.\n *\n * @param {string} path - The path to scope the new client to.\n *\n * @returns {BaseClient} A new client operating on a subpath of the current\n * base path.\n */\n scope: function (path) {\n return new BaseClient(this.storage, this.makePath(path));\n },\n\n /**\n * Get a list of child nodes below a given path.\n *\n * @param {string} path - The path to query. It MUST end with a forward slash.\n * @param {number} maxAge - (optional) Either ``false`` or the maximum age of\n * cached listing in milliseconds. See :ref:`max-age`.\n *\n * @returns {Promise} A promise for an object representing child nodes\n */\n getListing: function (path, maxAge) {\n if (typeof(path) !== 'string') {\n path = '';\n } else if (path.length > 0 && path[path.length - 1] !== '/') {\n return Promise.reject(\"Not a folder: \" + path);\n }\n return this.storage.get(this.makePath(path), maxAge).then(\n function (r) {\n return (r.statusCode === 404) ? {} : r.body;\n }\n );\n },\n\n /**\n * Get all objects directly below a given path.\n *\n * @param {string} path - Path to the folder. Must end in a forward slash.\n * @param {number} maxAge - (optional) Either ``false`` or the maximum age of\n * cached objects in milliseconds. See :ref:`max-age`.\n *\n * @returns {Promise} A promise for an object\n */\n getAll: function (path, maxAge) {\n if (typeof(path) !== 'string') {\n path = '';\n } else if (path.length > 0 && path[path.length - 1] !== '/') {\n return Promise.reject(\"Not a folder: \" + path);\n }\n\n return this.storage.get(this.makePath(path), maxAge).then(function (r) {\n if (r.statusCode === 404) { return {}; }\n if (typeof(r.body) === 'object') {\n var keys = Object.keys(r.body);\n if (keys.length === 0) {\n // treat this like 404. it probably means a folder listing that\n // has changes that haven't been pushed out yet.\n return {};\n }\n\n var calls = keys.map(function (key) {\n return this.storage.get(this.makePath(path + key), maxAge)\n .then(function (o) {\n if (typeof(o.body) === 'string') {\n try {\n o.body = JSON.parse(o.body);\n } catch (e) {\n // empty\n }\n }\n if (typeof(o.body) === 'object') {\n r.body[key] = o.body;\n }\n });\n }.bind(this));\n return Promise.all(calls).then(function () {\n return r.body;\n });\n }\n }.bind(this));\n },\n\n /**\n * Get the file at the given path. A file is raw data, as opposed to\n * a JSON object (use :func:`getObject` for that).\n *\n * @param {string} path - Relative path from the module root (without leading\n * slash).\n * @param {number} maxAge - (optional) Either ``false`` or the maximum age of\n * the cached file in milliseconds. See :ref:`max-age`.\n *\n * @returns {Promise} A promise for an object\n */\n getFile: function (path, maxAge) {\n if (typeof(path) !== 'string') {\n return Promise.reject('Argument \\'path\\' of baseClient.getFile must be a string');\n }\n return this.storage.get(this.makePath(path), maxAge).then(function (r) {\n return {\n data: r.body,\n contentType: r.contentType,\n revision: r.revision // (this is new)\n };\n });\n },\n\n /**\n * Store raw data at a given path.\n *\n * @param {string} mimeType - MIME media type of the data being stored\n * @param {string} path - Path relative to the module root\n * @param {string|ArrayBuffer|ArrayBufferView} body - Raw data to store\n *\n * @returns {Promise} A promise for the created/updated revision (ETag)\n */\n storeFile: function (mimeType, path, body) {\n if (typeof(mimeType) !== 'string') {\n return Promise.reject('Argument \\'mimeType\\' of baseClient.storeFile must be a string');\n }\n if (typeof(path) !== 'string') {\n return Promise.reject('Argument \\'path\\' of baseClient.storeFile must be a string');\n }\n if (typeof(body) !== 'string' && typeof(body) !== 'object') {\n return Promise.reject('Argument \\'body\\' of baseClient.storeFile must be a string, ArrayBuffer, or ArrayBufferView');\n }\n if (!this.storage.access.checkPathPermission(this.makePath(path), 'rw')) {\n console.warn('WARNING: Editing a document to which only read access (\\'r\\') was claimed');\n }\n\n return this.storage.put(this.makePath(path), body, mimeType).then(function (r) {\n if (r.statusCode === 200 || r.statusCode === 201) {\n return r.revision;\n } else {\n return Promise.reject(\"Request (PUT \" + this.makePath(path) + \") failed with status: \" + r.statusCode);\n }\n }.bind(this));\n },\n\n /**\n * Get a JSON object from the given path.\n *\n * @param {string} path - Relative path from the module root (without leading\n * slash).\n * @param {number} maxAge - (optional) Either ``false`` or the maximum age of\n * cached object in milliseconds. See :ref:`max-age`.\n *\n * @returns {Promise} A promise, which resolves with the requested object (or ``null``\n * if non-existent)\n */\n getObject: function (path, maxAge) {\n if (typeof(path) !== 'string') {\n return Promise.reject('Argument \\'path\\' of baseClient.getObject must be a string');\n }\n return this.storage.get(this.makePath(path), maxAge).then(function (r) {\n if (typeof(r.body) === 'object') { // will be the case for documents stored with rs.js <= 0.10.0-beta2\n return r.body;\n } else if (typeof(r.body) === 'string') {\n try {\n return JSON.parse(r.body);\n } catch (e) {\n throw \"Not valid JSON: \" + this.makePath(path);\n }\n } else if (typeof(r.body) !== 'undefined' && r.statusCode === 200) {\n return Promise.reject(\"Not an object: \" + this.makePath(path));\n }\n }.bind(this));\n },\n\n /**\n * Store object at given path. Triggers synchronization.\n *\n * See ``declareType()`` and :doc:`data types `\n * for an explanation of types\n *\n * @param {string} type - Unique type of this object within this module.\n * @param {string} path - Path relative to the module root.\n * @param {object} object - A JavaScript object to be stored at the given\n * path. Must be serializable as JSON.\n *\n * @returns {Promise} Resolves with revision on success. Rejects with\n * a ValidationError, if validations fail.\n */\n storeObject: function (typeAlias, path, object) {\n if (typeof(typeAlias) !== 'string') {\n return Promise.reject('Argument \\'typeAlias\\' of baseClient.storeObject must be a string');\n }\n if (typeof(path) !== 'string') {\n return Promise.reject('Argument \\'path\\' of baseClient.storeObject must be a string');\n }\n if (typeof(object) !== 'object') {\n return Promise.reject('Argument \\'object\\' of baseClient.storeObject must be an object');\n }\n\n this._attachType(object, typeAlias);\n\n try {\n var validationResult = this.validate(object);\n if (! validationResult.valid) {\n return Promise.reject(validationResult);\n }\n } catch(exc) {\n return Promise.reject(exc);\n }\n\n return this.storage.put(this.makePath(path), JSON.stringify(object), 'application/json; charset=UTF-8').then(function (r) {\n if (r.statusCode === 200 || r.statusCode === 201) {\n return r.revision;\n } else {\n return Promise.reject(\"Request (PUT \" + this.makePath(path) + \") failed with status: \" + r.statusCode);\n }\n }.bind(this));\n },\n\n /**\n * Remove node at given path from storage. Triggers synchronization.\n *\n * @param {string} path - Path relative to the module root.\n * @returns {Promise}\n */\n remove: function (path) {\n if (typeof(path) !== 'string') {\n return Promise.reject('Argument \\'path\\' of baseClient.remove must be a string');\n }\n if (!this.storage.access.checkPathPermission(this.makePath(path), 'rw')) {\n console.warn('WARNING: Removing a document to which only read access (\\'r\\') was claimed');\n }\n\n return this.storage.delete(this.makePath(path));\n },\n\n /**\n * Retrieve full URL of a document. Useful for example for sharing the public\n * URL of an item in the ``/public`` folder.\n *\n * @param {string} path - Path relative to the module root.\n * @returns {string} The full URL of the item, including the storage origin\n */\n getItemURL: function (path) {\n if (typeof(path) !== 'string') {\n throw 'Argument \\'path\\' of baseClient.getItemURL must be a string';\n }\n if (this.storage.connected) {\n path = this._cleanPath( this.makePath(path) );\n return this.storage.remote.href + path;\n } else {\n return undefined;\n }\n },\n\n /**\n * Set caching strategy for a given path and its children.\n *\n * See :ref:`caching-strategies` for a detailed description of the available\n * strategies.\n *\n * @param {string} path - Path to cache\n * @param {string} strategy - Caching strategy. One of 'ALL', 'SEEN', or\n * 'FLUSH'. Defaults to 'ALL'.\n *\n * @returns {BaseClient} The same instance this is called on to allow for method chaining\n */\n cache: function (path, strategy) {\n if (typeof path !== 'string') {\n throw 'Argument \\'path\\' of baseClient.cache must be a string';\n }\n\n if (strategy === undefined) {\n strategy = 'ALL';\n } else if (typeof strategy !== 'string') {\n throw 'Argument \\'strategy\\' of baseClient.cache must be a string or undefined';\n }\n if (strategy !== 'FLUSH' &&\n strategy !== 'SEEN' &&\n strategy !== 'ALL') {\n throw 'Argument \\'strategy\\' of baseclient.cache must be one of '\n + '[\"FLUSH\", \"SEEN\", \"ALL\"]';\n }\n this.storage.caching.set(this.makePath(path), strategy);\n\n return this;\n },\n\n /**\n * TODO: document\n *\n * @param {string} path\n */\n flush: function (path) {\n return this.storage.local.flush(path);\n },\n\n /**\n * Declare a remoteStorage object type using a JSON schema.\n *\n * See :doc:`Defining data types ` for more info.\n *\n * @param {string} alias - A type alias/shortname\n * @param {uri} uri - (optional) JSON-LD URI of the schema. Automatically generated if none given\n * @param {object} schema - A JSON Schema object describing the object type\n **/\n declareType: function(alias, uri, schema) {\n if (! schema) {\n schema = uri;\n uri = this._defaultTypeURI(alias);\n }\n BaseClient.Types.declare(this.moduleName, alias, uri, schema);\n },\n\n /**\n * Validate an object against the associated schema.\n *\n * @param {Object} object - JS object to validate. Must have a ``@context`` property.\n *\n * @returns {Object} An object containing information about validation errors\n **/\n validate: function(object) {\n var schema = BaseClient.Types.getSchema(object['@context']);\n if (schema) {\n return tv4.validateResult(object, schema);\n } else {\n throw new SchemaNotFound(object['@context']);\n }\n },\n\n /**\n * TODO document\n *\n * @private\n */\n schemas: {\n configurable: true,\n get: function() {\n return BaseClient.Types.inScope(this.moduleName);\n }\n },\n\n /**\n * The default JSON-LD @context URL for RS types/objects/documents\n *\n * @private\n */\n _defaultTypeURI: function(alias) {\n return 'http://remotestorage.io/spec/modules/' + encodeURIComponent(this.moduleName) + '/' + encodeURIComponent(alias);\n },\n\n /**\n * Attaches the JSON-LD @content to an object\n *\n * @private\n */\n _attachType: function(object, alias) {\n object['@context'] = BaseClient.Types.resolveAlias(this.moduleName + '/' + alias) || this._defaultTypeURI(alias);\n },\n\n /**\n * TODO: document\n *\n * @private\n */\n makePath: function (path) {\n return this.base + (path || '');\n },\n\n /**\n * TODO: document\n *\n * @private\n */\n _fireChange: function (event) {\n if (config.changeEvents[event.origin]) {\n ['new', 'old', 'lastCommon'].forEach(function (fieldNamePrefix) {\n if ((!event[fieldNamePrefix+'ContentType'])\n || (/^application\\/(.*)json(.*)/.exec(event[fieldNamePrefix+'ContentType']))) {\n if (typeof(event[fieldNamePrefix+'Value']) === 'string') {\n try {\n event[fieldNamePrefix+'Value'] = JSON.parse(event[fieldNamePrefix+'Value']);\n } catch(e) {\n // empty\n }\n }\n }\n });\n this._emit('change', event);\n }\n },\n\n /**\n * TODO: document\n *\n * @private\n */\n _cleanPath: util.cleanPath\n\n};\n\nBaseClient._rs_init = function () {};\n\nmodule.exports = BaseClient;\n","'use strict';\n\nconst log = require('./log');\nconst util = require('./util');\nconst eventHandling = require('./eventhandling');\nconst Authorize = require('./authorize');\nconst config = require('./config');\n\n/**\n * This file exposes a get/put/delete interface on top of fetch() or XMLHttpRequest.\n * It requires to be configured with parameters about the remotestorage server to\n * connect to.\n * Each instance of WireClient is always associated with a single remotestorage\n * server and access token.\n *\n * Usually the WireClient instance can be accessed via `remoteStorage.remote`.\n *\n * This is the get/put/delete interface:\n *\n * - #get() takes a path and optionally a ifNoneMatch option carrying a version\n * string to check. It returns a promise that will be fulfilled with the HTTP\n * response status, the response body, the MIME type as returned in the\n * 'Content-Type' header and the current revision, as returned in the 'ETag'\n * header.\n * - #put() takes a path, the request body and a content type string. It also\n * accepts the ifMatch and ifNoneMatch options, that map to the If-Match and\n * If-None-Match headers respectively. See the remotestorage-01 specification\n * for details on handling these headers. It returns a promise, fulfilled with\n * the same values as the one for #get().\n * - #delete() takes a path and the ifMatch option as well. It returns a promise\n * fulfilled with the same values as the one for #get().\n *\n * In addition to this, the WireClient has some compatibility features to work with\n * remotestorage 2012.04 compatible storages. For example it will cache revisions\n * from folder listings in-memory and return them accordingly as the \"revision\"\n * parameter in response to #get() requests. Similarly it will return 404 when it\n * receives an empty folder listing, to mimic remotestorage-01 behavior. Note\n * that it is not always possible to know the revision beforehand, hence it may\n * be undefined at times (especially for caching-roots).\n *\n * @interface\n */\n\nvar hasLocalStorage;\nvar SETTINGS_KEY = 'remotestorage:wireclient';\n\nvar API_2012 = 1, API_00 = 2, API_01 = 3, API_02 = 4, API_HEAD = 5;\n\nvar STORAGE_APIS = {\n 'draft-dejong-remotestorage-00': API_00,\n 'draft-dejong-remotestorage-01': API_01,\n 'draft-dejong-remotestorage-02': API_02,\n 'https://www.w3.org/community/rww/wiki/read-write-web-00#simple': API_2012\n};\n\nvar isArrayBufferView;\n\nif (typeof(ArrayBufferView) === 'function') {\n isArrayBufferView = function (object) { return object && (object instanceof ArrayBufferView); };\n} else {\n var arrayBufferViews = [\n Int8Array, Uint8Array, Int16Array, Uint16Array,\n Int32Array, Uint32Array, Float32Array, Float64Array\n ];\n isArrayBufferView = function (object) {\n for (let i=0;i<8;i++) {\n if (object instanceof arrayBufferViews[i]) {\n return true;\n }\n }\n return false;\n };\n}\n\nconst isFolder = util.isFolder;\nconst cleanPath = util.cleanPath;\nconst shouldBeTreatedAsBinary = util.shouldBeTreatedAsBinary;\nconst getJSONFromLocalStorage = util.getJSONFromLocalStorage;\nconst getTextFromArrayBuffer = util.getTextFromArrayBuffer;\n\nfunction addQuotes(str) {\n if (typeof(str) !== 'string') {\n return str;\n }\n if (str === '*') {\n return '*';\n }\n\n return '\"' + str + '\"';\n}\n\nfunction stripQuotes(str) {\n if (typeof(str) !== 'string') {\n return str;\n }\n\n return str.replace(/^[\"']|[\"']$/g, '');\n}\n\nfunction determineCharset(mimeType) {\n var charset = 'UTF-8';\n var charsetMatch;\n\n if (mimeType) {\n charsetMatch = mimeType.match(/charset=(.+)$/);\n if (charsetMatch) {\n charset = charsetMatch[1];\n }\n }\n return charset;\n}\n\nfunction isFolderDescription(body) {\n return ((body['@context'] === 'http://remotestorage.io/spec/folder-description')\n && (typeof(body['items']) === 'object'));\n}\n\nfunction isSuccessStatus(status) {\n return [201, 204, 304].indexOf(status) >= 0;\n}\n\nfunction isErrorStatus(status) {\n return [401, 403, 404, 412].indexOf(status) >= 0;\n}\n\n/**\n * Class : WireClient\n **/\nvar WireClient = function WireClient(rs) {\n this.rs = rs;\n this.connected = false;\n\n /**\n * Event: connected\n * Fired when the wireclient connect method realizes that it is in\n * possession of a token and href\n **/\n eventHandling(this, 'connected', 'not-connected');\n\n if (hasLocalStorage) {\n const settings = getJSONFromLocalStorage(SETTINGS_KEY);\n if (settings) {\n setTimeout(function () {\n this.configure(settings);\n }.bind(this), 0);\n }\n }\n\n this._revisionCache = {};\n\n if (this.connected) {\n setTimeout(this._emit.bind(this), 0, 'connected');\n }\n};\n\nWireClient.prototype = {\n /**\n * Property: token\n *\n * Holds the bearer token of this WireClient, as obtained in the OAuth dance\n *\n * Example:\n * (start code)\n *\n * remoteStorage.remote.token\n * // -> 'DEADBEEF01=='\n */\n\n /**\n * Property: href\n *\n * Holds the server's base URL, as obtained in the Webfinger discovery\n *\n * Example:\n * (start code)\n *\n * remoteStorage.remote.href\n * // -> 'https://storage.example.com/users/jblogg/'\n */\n\n /**\n * Property: storageApi\n *\n * Holds the spec version the server claims to be compatible with\n *\n * Example:\n * (start code)\n *\n * remoteStorage.remote.storageApi\n * // -> 'draft-dejong-remotestorage-01'\n */\n\n _request: function (method, uri, token, headers, body, getEtag, fakeRevision) {\n if ((method === 'PUT' || method === 'DELETE') && uri[uri.length - 1] === '/') {\n return Promise.reject('Don\\'t ' + method + ' on directories!');\n }\n\n var revision;\n var self = this;\n\n if (token !== Authorize.IMPLIED_FAKE_TOKEN) {\n headers['Authorization'] = 'Bearer ' + token;\n }\n\n this.rs._emit('wire-busy', {\n method: method,\n isFolder: isFolder(uri)\n });\n\n return WireClient.request(method, uri, {\n body: body,\n headers: headers,\n responseType: 'arraybuffer'\n }).then(function(response) {\n if (!self.online) {\n self.online = true;\n self.rs._emit('network-online');\n }\n self.rs._emit('wire-done', {\n method: method,\n isFolder: isFolder(uri),\n success: true\n });\n\n if (isErrorStatus(response.status)) {\n log('[WireClient] Error response status', response.status);\n if (getEtag) {\n revision = stripQuotes(response.getResponseHeader('ETag'));\n } else {\n revision = undefined;\n }\n\n if (response.status === 401) {\n self.rs._emit('error', new Authorize.Unauthorized());\n }\n\n return Promise.resolve({statusCode: response.status, revision: revision});\n } else if (isSuccessStatus(response.status) ||\n (response.status === 200 && method !== 'GET')) {\n revision = stripQuotes(response.getResponseHeader('ETag'));\n log('[WireClient] Successful request', revision);\n return Promise.resolve({statusCode: response.status, revision: revision});\n } else {\n var mimeType = response.getResponseHeader('Content-Type');\n if (getEtag) {\n revision = stripQuotes(response.getResponseHeader('ETag'));\n } else {\n revision = response.status === 200 ? fakeRevision : undefined;\n }\n\n var charset = determineCharset(mimeType);\n\n if (shouldBeTreatedAsBinary(response.response, mimeType)) {\n log('[WireClient] Successful request with unknown or binary mime-type', revision);\n return Promise.resolve({\n statusCode: response.status,\n body: response.response,\n contentType: mimeType,\n revision: revision\n });\n } else {\n return getTextFromArrayBuffer(response.response, charset)\n .then(function (textContent) {\n log('[WireClient] Successful request', revision);\n return Promise.resolve({\n statusCode: response.status,\n body: textContent,\n contentType: mimeType,\n revision: revision\n });\n });\n }\n }\n }, function (error) {\n if (self.online) {\n self.online = false;\n self.rs._emit('network-offline');\n }\n self.rs._emit('wire-done', {\n method: method,\n isFolder: isFolder(uri),\n success: false\n });\n\n return Promise.reject(error);\n });\n },\n\n /**\n *\n * Method: configure\n *\n * Sets the userAddress, href, storageApi, token, and properties of a\n * remote store. Also sets connected and online to true and emits the\n * 'connected' event, if both token and href are present.\n *\n * Parameters:\n * settings - An object that may contain userAddress (string or null),\n * href (string or null), storageApi (string or null), token (string\n * or null), and/or properties (the JSON-parsed properties object\n * from the user's WebFinger record, see section 10 of\n * http://tools.ietf.org/html/draft-dejong-remotestorage-03\n * or null).\n * Fields that are not included (i.e. `undefined`), stay at\n * their current value. To set a field, include that field\n * with a `string` value. To reset a field, for instance when\n * the user disconnected their storage, or you found that the\n * token you have has expired, simply set that field to `null`.\n */\n configure: function (settings) {\n if (typeof settings !== 'object') {\n throw new Error('WireClient configure settings parameter should be an object');\n }\n if (typeof settings.userAddress !== 'undefined') {\n this.userAddress = settings.userAddress;\n }\n if (typeof settings.href !== 'undefined') {\n this.href = settings.href;\n }\n if (typeof settings.storageApi !== 'undefined') {\n this.storageApi = settings.storageApi;\n }\n if (typeof settings.token !== 'undefined') {\n this.token = settings.token;\n }\n if (typeof settings.properties !== 'undefined') {\n this.properties = settings.properties;\n }\n\n if (typeof this.storageApi !== 'undefined') {\n this._storageApi = STORAGE_APIS[this.storageApi] || API_HEAD;\n this.supportsRevs = this._storageApi >= API_00;\n }\n if (this.href && this.token) {\n this.connected = true;\n this.online = true;\n this._emit('connected');\n } else {\n this.connected = false;\n }\n if (hasLocalStorage) {\n localStorage[SETTINGS_KEY] = JSON.stringify({\n userAddress: this.userAddress,\n href: this.href,\n storageApi: this.storageApi,\n token: this.token,\n properties: this.properties\n });\n }\n },\n\n stopWaitingForToken: function () {\n if (!this.connected) {\n this._emit('not-connected');\n }\n },\n\n get: function (path, options) {\n var self = this;\n if (!this.connected) {\n return Promise.reject('not connected (path: ' + path + ')');\n }\n if (!options) { options = {}; }\n var headers = {};\n if (this.supportsRevs) {\n if (options.ifNoneMatch) {\n headers['If-None-Match'] = addQuotes(options.ifNoneMatch);\n }\n }\n // commenting it out as this is doing nothing and jshint is complaining -les\n // else if (options.ifNoneMatch) {\n // var oldRev = this._revisionCache[path];\n // }\n\n\n return this._request('GET', this.href + cleanPath(path), this.token, headers,\n undefined, this.supportsRevs, this._revisionCache[path])\n .then(function (r) {\n if (!isFolder(path)) {\n return Promise.resolve(r);\n }\n var itemsMap = {};\n if (typeof(r.body) !== 'undefined') {\n try {\n r.body = JSON.parse(r.body);\n } catch (e) {\n return Promise.reject('Folder description at ' + self.href + cleanPath(path) + ' is not JSON');\n }\n }\n\n if (r.statusCode === 200 && typeof(r.body) === 'object') {\n // New folder listing received\n if (Object.keys(r.body).length === 0) {\n // Empty folder listing of any spec\n r.statusCode = 404;\n } else if (isFolderDescription(r.body)) {\n // >= 02 spec\n for (var item in r.body.items) {\n self._revisionCache[path + item] = r.body.items[item].ETag;\n }\n itemsMap = r.body.items;\n } else {\n // < 02 spec\n Object.keys(r.body).forEach(function (key){\n self._revisionCache[path + key] = r.body[key];\n itemsMap[key] = {'ETag': r.body[key]};\n });\n }\n r.body = itemsMap;\n return Promise.resolve(r);\n } else {\n return Promise.resolve(r);\n }\n });\n },\n\n put: function (path, body, contentType, options) {\n if (!this.connected) {\n return Promise.reject('not connected (path: ' + path + ')');\n }\n if (!options) { options = {}; }\n if ((!contentType.match(/charset=/)) && (body instanceof ArrayBuffer || isArrayBufferView(body))) {\n contentType += '; charset=binary';\n }\n var headers = { 'Content-Type': contentType };\n if (this.supportsRevs) {\n if (options.ifMatch) {\n headers['If-Match'] = addQuotes(options.ifMatch);\n }\n if (options.ifNoneMatch) {\n headers['If-None-Match'] = addQuotes(options.ifNoneMatch);\n }\n }\n return this._request('PUT', this.href + cleanPath(path), this.token,\n headers, body, this.supportsRevs);\n },\n\n 'delete': function (path, options) {\n if (!this.connected) {\n throw new Error('not connected (path: ' + path + ')');\n }\n if (!options) { options = {}; }\n var headers = {};\n if (this.supportsRevs) {\n if (options.ifMatch) {\n headers['If-Match'] = addQuotes(options.ifMatch);\n }\n }\n return this._request('DELETE', this.href + cleanPath(path), this.token,\n headers,\n undefined, this.supportsRevs);\n }\n};\n\n// Shared isArrayBufferView used by WireClient and Dropbox\nWireClient.isArrayBufferView = isArrayBufferView;\n\n// Shared request function used by WireClient, GoogleDrive and Dropbox.\nWireClient.request = function (method, url, options) {\n if (typeof fetch === 'function') {\n return WireClient._fetchRequest(method, url, options);\n } else if (typeof XMLHttpRequest === 'function') {\n return WireClient._xhrRequest(method, url, options);\n } else {\n log('[WireClient] add a polyfill for fetch or XMLHttpRequest');\n return Promise.reject('[WireClient] add a polyfill for fetch or XMLHttpRequest');\n }\n};\n\n/** options includes body, headers and responseType */\nWireClient._fetchRequest = function (method, url, options) {\n var syntheticXhr;\n var responseHeaders = {};\n var abortController;\n if (typeof AbortController === 'function') {\n abortController = new AbortController();\n }\n var networkPromise = fetch(url, {\n method: method,\n headers: options.headers,\n body: options.body,\n signal: abortController ? abortController.signal : undefined\n }).then(function (response) {\n log('[WireClient fetch]', response);\n\n response.headers.forEach(function (value, headerName) {\n responseHeaders[headerName.toUpperCase()] = value;\n });\n\n syntheticXhr = {\n readyState: 4,\n status: response.status,\n statusText: response.statusText,\n response: undefined,\n getResponseHeader: function (headerName) {\n return responseHeaders[headerName.toUpperCase()] || null;\n },\n // responseText: 'foo',\n responseType: options.responseType,\n responseURL: url,\n };\n switch (options.responseType) {\n case 'arraybuffer':\n return response.arrayBuffer();\n case 'blob':\n return response.blob();\n case 'json':\n return response.json();\n case undefined:\n case '':\n case 'text':\n return response.text();\n default: // document\n throw new Error(\"responseType 'document' is not currently supported using fetch\");\n }\n }).then(function (processedBody) {\n syntheticXhr.response = processedBody;\n if (!options.responseType || options.responseType === 'text') {\n syntheticXhr.responseText = processedBody;\n }\n return syntheticXhr;\n });\n\n var timeoutPromise = new Promise(function (resolve, reject) {\n setTimeout(function () {\n reject('timeout');\n if (abortController) {\n abortController.abort();\n }\n }, config.requestTimeout);\n });\n\n return Promise.race([networkPromise, timeoutPromise]);\n};\n\nWireClient._xhrRequest = function (method, url, options) {\n return new Promise ((resolve, reject) => {\n\n log('[WireClient]', method, url);\n\n var timedOut = false;\n\n var timer = setTimeout(function () {\n timedOut = true;\n reject('timeout');\n }, config.requestTimeout);\n\n var xhr = new XMLHttpRequest();\n xhr.open(method, url, true);\n\n if (options.responseType) {\n xhr.responseType = options.responseType;\n }\n\n if (options.headers) {\n for (var key in options.headers) {\n xhr.setRequestHeader(key, options.headers[key]);\n }\n }\n\n xhr.onload = () => {\n if (timedOut) { return; }\n clearTimeout(timer);\n resolve(xhr);\n };\n\n xhr.onerror = (error) => {\n if (timedOut) { return; }\n clearTimeout(timer);\n reject(error);\n };\n\n var body = options.body;\n\n if (typeof(body) === 'object' && !isArrayBufferView(body) && body instanceof ArrayBuffer) {\n body = new Uint8Array(body);\n }\n xhr.send(body);\n });\n};\n\nObject.defineProperty(WireClient.prototype, 'storageType', {\n get: function () {\n if (this.storageApi) {\n var spec = this.storageApi.match(/draft-dejong-(remotestorage-\\d\\d)/);\n return spec ? spec[1] : '2012.04';\n } else {\n return undefined;\n }\n }\n});\n\n\nWireClient._rs_init = function (remoteStorage) {\n hasLocalStorage = util.localStorageAvailable();\n remoteStorage.remote = new WireClient(remoteStorage);\n this.online = true;\n};\n\nWireClient._rs_supported = function () {\n return typeof fetch === 'function' || typeof XMLHttpRequest === 'function';\n};\n\nWireClient._rs_cleanup = function () {\n if (hasLocalStorage){\n delete localStorage[SETTINGS_KEY];\n }\n};\n\nmodule.exports = WireClient;\n","const { isFolder, isDocument, equal, deepClone, pathsFromRoot } = require('./util');\nconst Env = require('./env');\nconst eventHandling = require('./eventhandling');\nconst log = require('./log');\nconst Authorize = require('./authorize');\nconst config = require('./config');\n\nlet syncCycleCb, syncOnConnect;\n\nfunction taskFor (action, path, promise) {\n return {\n action: action,\n path: path,\n promise: promise\n };\n}\n\nfunction nodeChanged (node, etag) {\n return node.common.revision !== etag &&\n (!node.remote || node.remote.revision !== etag);\n}\n\nfunction isStaleChild (node) {\n return node.remote && node.remote.revision && !node.remote.itemsMap && !node.remote.body;\n}\n\nfunction hasCommonRevision (node) {\n return node.common && node.common.revision;\n}\n\nfunction hasNoRemoteChanges (node) {\n if (node.remote && node.remote.revision &&\n node.remote.revision !== node.common.revision) {\n return false;\n }\n return (node.common.body === undefined && node.remote.body === false) ||\n (node.remote.body === node.common.body &&\n node.remote.contentType === node.common.contentType);\n}\n\nfunction mergeMutualDeletion (node) {\n if (node.remote && node.remote.body === false &&\n node.local && node.local.body === false) {\n delete node.local;\n }\n return node;\n}\n\nfunction handleVisibility (rs) {\n function handleChange(isForeground) {\n var oldValue, newValue;\n oldValue = rs.getCurrentSyncInterval();\n config.isBackground = !isForeground;\n newValue = rs.getCurrentSyncInterval();\n rs._emit('sync-interval-change', {oldValue: oldValue, newValue: newValue});\n }\n Env.on('background', () => handleChange(false));\n Env.on('foreground', () => handleChange(true));\n}\n\n/**\n * Class: RemoteStorage.Sync\n *\n * This class basically does six things:\n *\n * - retrieve the remote version of relevant documents and folders\n * - add all local and remote documents together into one tree\n * - push local documents out if they don't exist remotely\n * - push local changes out to remote documents (conditionally, to avoid race\n * conditions where both have changed)\n * - adopt the local version of a document to its remote version if both exist\n * and they differ\n * - delete the local version of a document if it was deleted remotely\n * - if any GET requests were waiting for remote data, resolve them once this\n * data comes in.\n *\n * It does this using requests to documents and folders. Whenever a folder GET\n * comes in, it gives information about all the documents it contains (this is\n * the `markChildren` function).\n **/\nclass Sync {\n constructor (remoteStorage) {\n this.rs = remoteStorage;\n\n this._tasks = {};\n this._running = {};\n this._timeStarted = {};\n\n this.numThreads = 10;\n\n this.rs.local.onDiff(path => {\n this.addTask(path);\n this.doTasks();\n });\n\n this.rs.caching.onActivate(path => {\n this.addTask(path);\n this.doTasks();\n });\n\n eventHandling(this, 'done', 'req-done');\n }\n\n now () {\n return new Date().getTime();\n }\n\n queueGetRequest (path) {\n return new Promise((resolve, reject) => {\n if (!this.rs.remote.connected) {\n reject('cannot fulfill maxAge requirement - remote is not connected');\n } else if (!this.rs.remote.online) {\n reject('cannot fulfill maxAge requirement - remote is not online');\n } else {\n this.addTask(path, function () {\n this.rs.local.get(path).then(function (r) {\n return resolve(r);\n });\n }.bind(this));\n\n this.doTasks();\n }\n });\n }\n\n // FIXME force02 sounds like rs spec 02, thus could be removed\n corruptServerItemsMap (itemsMap, force02) {\n if ((typeof(itemsMap) !== 'object') || (Array.isArray(itemsMap))) {\n return true;\n }\n\n for (var itemName in itemsMap) {\n var item = itemsMap[itemName];\n\n if (typeof(item) !== 'object') {\n return true;\n }\n if (typeof(item.ETag) !== 'string') {\n return true;\n }\n if (isFolder(itemName)) {\n if (itemName.substring(0, itemName.length-1).indexOf('/') !== -1) {\n return true;\n }\n } else {\n if (itemName.indexOf('/') !== -1) {\n return true;\n }\n if (force02) {\n if (typeof(item['Content-Type']) !== 'string') {\n return true;\n }\n if (typeof(item['Content-Length']) !== 'number') {\n return true;\n }\n }\n }\n }\n\n return false;\n }\n\n corruptItemsMap (itemsMap) {\n if ((typeof(itemsMap) !== 'object') || (Array.isArray(itemsMap))) {\n return true;\n }\n\n for (var itemName in itemsMap) {\n if (typeof(itemsMap[itemName]) !== 'boolean') {\n return true;\n }\n }\n\n return false;\n }\n\n corruptRevision (rev) {\n return ((typeof(rev) !== 'object') ||\n (Array.isArray(rev)) ||\n (rev.revision && typeof(rev.revision) !== 'string') ||\n (rev.body && typeof(rev.body) !== 'string' && typeof(rev.body) !== 'object') ||\n (rev.contentType && typeof(rev.contentType) !== 'string') ||\n (rev.contentLength && typeof(rev.contentLength) !== 'number') ||\n (rev.timestamp && typeof(rev.timestamp) !== 'number') ||\n (rev.itemsMap && this.corruptItemsMap(rev.itemsMap)));\n }\n\n isCorrupt (node) {\n return ((typeof(node) !== 'object') ||\n (Array.isArray(node)) ||\n (typeof(node.path) !== 'string') ||\n (this.corruptRevision(node.common)) ||\n (node.local && this.corruptRevision(node.local)) ||\n (node.remote && this.corruptRevision(node.remote)) ||\n (node.push && this.corruptRevision(node.push)));\n }\n\n hasTasks () {\n return Object.getOwnPropertyNames(this._tasks).length > 0;\n }\n\n collectDiffTasks () {\n var num = 0;\n\n return this.rs.local.forAllNodes(node => {\n if (num > 100) { return; }\n\n if (this.isCorrupt(node)) {\n log('[Sync] WARNING: corrupt node in local cache', node);\n if (typeof(node) === 'object' && node.path) {\n this.addTask(node.path);\n num++;\n }\n } else if (this.needsFetch(node) && this.rs.access.checkPathPermission(node.path, 'r')) {\n this.addTask(node.path);\n num++;\n } else if (isDocument(node.path) && this.needsPush(node) &&\n this.rs.access.checkPathPermission(node.path, 'rw')) {\n this.addTask(node.path);\n num++;\n }\n }).then(function () {\n return num;\n }, function (err) {\n throw err;\n });\n }\n\n inConflict (node) {\n return (node.local && node.remote &&\n (node.remote.body !== undefined || node.remote.itemsMap));\n }\n\n needsRefresh (node) {\n if (node.common) {\n if (!node.common.timestamp) {\n return true;\n }\n return (this.now() - node.common.timestamp > config.syncInterval);\n }\n return false;\n }\n\n needsFetch (node) {\n if (this.inConflict(node)) {\n return true;\n }\n if (node.common &&\n node.common.itemsMap === undefined &&\n node.common.body === undefined) {\n return true;\n }\n if (node.remote &&\n node.remote.itemsMap === undefined &&\n node.remote.body === undefined) {\n return true;\n }\n return false;\n }\n\n needsPush (node) {\n if (this.inConflict(node)) {\n return false;\n }\n if (node.local && !node.push) {\n return true;\n }\n }\n\n needsRemotePut (node) {\n return node.local && node.local.body;\n }\n\n needsRemoteDelete (node) {\n return node.local && node.local.body === false;\n }\n\n getParentPath (path) {\n var parts = path.match(/^(.*\\/)([^\\/]+\\/?)$/);\n\n if (parts) {\n return parts[1];\n } else {\n throw new Error('Not a valid path: \"'+path+'\"');\n }\n }\n\n deleteChildPathsFromTasks () {\n for (var path in this._tasks) {\n var paths = pathsFromRoot(path);\n\n for (var i=1; i {\n var parentPath;\n if (this.needsRefresh(node)) {\n try {\n parentPath = this.getParentPath(node.path);\n } catch(e) {\n // node.path is already '/', can't take parentPath\n }\n if (parentPath && this.rs.access.checkPathPermission(parentPath, 'r')) {\n this.addTask(parentPath);\n } else if (this.rs.access.checkPathPermission(node.path, 'r')) {\n this.addTask(node.path);\n }\n }\n }).then(() => { this.deleteChildPathsFromTasks(); },\n err => { throw err; });\n }\n\n flush (nodes) {\n for (var path in nodes) {\n // Strategy is 'FLUSH' and no local changes exist\n if (this.rs.caching.checkPath(path) === 'FLUSH' &&\n nodes[path] && !nodes[path].local) {\n log('[Sync] Flushing', path);\n nodes[path] = undefined; // Cause node to be flushed from cache\n }\n }\n return nodes;\n }\n\n doTask (path) {\n return this.rs.local.getNodes([path]).then(nodes => {\n var node = nodes[path];\n // First fetch:\n if (typeof(node) === 'undefined') {\n return taskFor('get', path, this.rs.remote.get(path));\n }\n // Fetch known-stale child:\n else if (isStaleChild(node)) {\n return taskFor('get', path, this.rs.remote.get(path));\n }\n // Push PUT:\n else if (this.needsRemotePut(node)) {\n node.push = deepClone(node.local);\n node.push.timestamp = this.now();\n\n return this.rs.local.setNodes(this.flush(nodes)).then(() => {\n var options;\n if (hasCommonRevision(node)) {\n options = { ifMatch: node.common.revision };\n } else {\n // Initial PUT (fail if something is already there)\n options = { ifNoneMatch: '*' };\n }\n\n return taskFor('put', path,\n this.rs.remote.put(path, node.push.body, node.push.contentType, options)\n );\n });\n }\n // Push DELETE:\n else if (this.needsRemoteDelete(node)) {\n node.push = { body: false, timestamp: this.now() };\n\n return this.rs.local.setNodes(this.flush(nodes)).then(() => {\n if (hasCommonRevision(node)) {\n return taskFor('delete', path,\n this.rs.remote.delete(path, { ifMatch: node.common.revision })\n );\n } else { // Ascertain current common or remote revision first\n return taskFor('get', path, this.rs.remote.get(path));\n }\n });\n }\n // Conditional refresh:\n else if (hasCommonRevision(node)) {\n return taskFor('get', path,\n this.rs.remote.get(path, { ifNoneMatch: node.common.revision })\n );\n }\n else {\n return taskFor('get', path, this.rs.remote.get(path));\n }\n });\n }\n\n autoMergeFolder (node) {\n if (node.remote.itemsMap) {\n node.common = node.remote;\n delete node.remote;\n\n if (node.common.itemsMap) {\n for (var itemName in node.common.itemsMap) {\n if (!node.local.itemsMap[itemName]) {\n // Indicates the node is either newly being fetched\n // has been deleted locally (whether or not leading to conflict);\n // before listing it in local listings, check if a local deletion\n // exists.\n node.local.itemsMap[itemName] = false;\n }\n }\n\n if (equal(node.local.itemsMap, node.common.itemsMap)) {\n delete node.local;\n }\n }\n }\n return node;\n }\n\n autoMergeDocument (node) {\n if (hasNoRemoteChanges(node)) {\n node = mergeMutualDeletion(node);\n delete node.remote;\n } else if (node.remote.body !== undefined) {\n // keep/revert:\n log('[Sync] Emitting keep/revert');\n\n this.rs.local._emitChange({\n origin: 'conflict',\n path: node.path,\n oldValue: node.local.body,\n newValue: node.remote.body,\n lastCommonValue: node.common.body,\n oldContentType: node.local.contentType,\n newContentType: node.remote.contentType,\n lastCommonContentType: node.common.contentType\n });\n\n if (node.remote.body) {\n node.common = node.remote;\n } else {\n node.common = {};\n }\n delete node.remote;\n delete node.local;\n }\n return node;\n }\n\n autoMerge (node) {\n if (node.remote) {\n if (node.local) {\n if (isFolder(node.path)) {\n return this.autoMergeFolder(node);\n } else {\n return this.autoMergeDocument(node);\n }\n } else { // no local changes\n if (isFolder(node.path)) {\n if (node.remote.itemsMap !== undefined) {\n node.common = node.remote;\n delete node.remote;\n }\n } else {\n if (node.remote.body !== undefined) {\n var change = {\n origin: 'remote',\n path: node.path,\n oldValue: (node.common.body === false ? undefined : node.common.body),\n newValue: (node.remote.body === false ? undefined : node.remote.body),\n oldContentType: node.common.contentType,\n newContentType: node.remote.contentType\n };\n if (change.oldValue || change.newValue) {\n this.rs.local._emitChange(change);\n }\n\n if (!node.remote.body) { // no remote, so delete/don't create\n return;\n }\n\n node.common = node.remote;\n delete node.remote;\n }\n }\n }\n } else {\n if (node.common.body) {\n this.rs.local._emitChange({\n origin: 'remote',\n path: node.path,\n oldValue: node.common.body,\n newValue: undefined,\n oldContentType: node.common.contentType,\n newContentType: undefined\n });\n }\n\n return undefined;\n }\n return node;\n }\n\n updateCommonTimestamp (path, revision) {\n return this.rs.local.getNodes([path]).then(nodes => {\n if (nodes[path] &&\n nodes[path].common &&\n nodes[path].common.revision === revision) {\n nodes[path].common.timestamp = this.now();\n }\n return this.rs.local.setNodes(this.flush(nodes));\n });\n }\n\n markChildren (path, itemsMap, changedNodes, missingChildren) {\n var paths = [];\n var meta = {};\n var recurse = {};\n\n for (var item in itemsMap) {\n paths.push(path+item);\n meta[path+item] = itemsMap[item];\n }\n for (var childName in missingChildren) {\n paths.push(path+childName);\n }\n\n return this.rs.local.getNodes(paths).then(nodes => {\n var cachingStrategy;\n var node;\n\n for (var nodePath in nodes) {\n node = nodes[nodePath];\n\n if (meta[nodePath]) {\n if (node && node.common) {\n if (nodeChanged(node, meta[nodePath].ETag)) {\n changedNodes[nodePath] = deepClone(node);\n changedNodes[nodePath].remote = {\n revision: meta[nodePath].ETag,\n timestamp: this.now()\n };\n changedNodes[nodePath] = this.autoMerge(changedNodes[nodePath]);\n }\n } else {\n cachingStrategy = this.rs.caching.checkPath(nodePath);\n if (cachingStrategy === 'ALL') {\n changedNodes[nodePath] = {\n path: nodePath,\n common: {\n timestamp: this.now()\n },\n remote: {\n revision: meta[nodePath].ETag,\n timestamp: this.now()\n }\n };\n }\n }\n\n if (changedNodes[nodePath] && meta[nodePath]['Content-Type']) {\n changedNodes[nodePath].remote.contentType = meta[nodePath]['Content-Type'];\n }\n\n if (changedNodes[nodePath] && meta[nodePath]['Content-Length']) {\n changedNodes[nodePath].remote.contentLength = meta[nodePath]['Content-Length'];\n }\n } else if (missingChildren[nodePath.substring(path.length)] && node && node.common) {\n if (node.common.itemsMap) {\n for (var commonItem in node.common.itemsMap) {\n recurse[nodePath+commonItem] = true;\n }\n }\n\n if (node.local && node.local.itemsMap) {\n for (var localItem in node.local.itemsMap) {\n recurse[nodePath+localItem] = true;\n }\n }\n\n if (node.remote || isFolder(nodePath)) {\n changedNodes[nodePath] = undefined;\n } else {\n changedNodes[nodePath] = this.autoMerge(node);\n\n if (typeof changedNodes[nodePath] === 'undefined') {\n var parentPath = this.getParentPath(nodePath);\n var parentNode = changedNodes[parentPath];\n var itemName = nodePath.substring(path.length);\n if (parentNode && parentNode.local) {\n delete parentNode.local.itemsMap[itemName];\n\n if (equal(parentNode.local.itemsMap, parentNode.common.itemsMap)) {\n delete parentNode.local;\n }\n }\n }\n }\n }\n }\n\n return this.deleteRemoteTrees(Object.keys(recurse), changedNodes)\n .then(changedObjs2 => {\n return this.rs.local.setNodes(this.flush(changedObjs2));\n });\n });\n }\n\n deleteRemoteTrees (paths, changedNodes) {\n if (paths.length === 0) {\n return Promise.resolve(changedNodes);\n }\n\n return this.rs.local.getNodes(paths).then(nodes => {\n var subPaths = {};\n\n var collectSubPaths = function (folder, path) {\n if (folder && folder.itemsMap) {\n for (var itemName in folder.itemsMap) {\n subPaths[path+itemName] = true;\n }\n }\n };\n\n for (var path in nodes) {\n var node = nodes[path];\n\n // TODO Why check for the node here? I don't think this check ever applies\n if (!node) {\n continue;\n }\n\n if (isFolder(path)) {\n collectSubPaths(node.common, path);\n collectSubPaths(node.local, path);\n } else {\n if (node.common && typeof(node.common.body) !== undefined) {\n changedNodes[path] = deepClone(node);\n changedNodes[path].remote = {\n body: false,\n timestamp: this.now()\n };\n changedNodes[path] = this.autoMerge(changedNodes[path]);\n }\n }\n }\n\n // Recurse whole tree depth levels at once:\n return this.deleteRemoteTrees(Object.keys(subPaths), changedNodes)\n .then(changedNodes2 => {\n return this.rs.local.setNodes(this.flush(changedNodes2));\n });\n });\n }\n\n completeFetch (path, bodyOrItemsMap, contentType, revision) {\n var paths;\n var parentPath;\n var pathsFromRootArr = pathsFromRoot(path);\n\n if (isFolder(path)) {\n paths = [path];\n } else {\n parentPath = pathsFromRootArr[1];\n paths = [path, parentPath];\n }\n\n return this.rs.local.getNodes(paths).then(nodes => {\n var itemName;\n var missingChildren = {};\n var node = nodes[path];\n var parentNode;\n\n var collectMissingChildren = function (folder) {\n if (folder && folder.itemsMap) {\n for (itemName in folder.itemsMap) {\n if (!bodyOrItemsMap[itemName]) {\n missingChildren[itemName] = true;\n }\n }\n }\n };\n\n if (typeof(node) !== 'object' ||\n node.path !== path ||\n typeof(node.common) !== 'object') {\n node = { path: path, common: {} };\n nodes[path] = node;\n }\n\n node.remote = {\n revision: revision,\n timestamp: this.now()\n };\n\n if (isFolder(path)) {\n collectMissingChildren(node.common);\n collectMissingChildren(node.remote);\n\n node.remote.itemsMap = {};\n for (itemName in bodyOrItemsMap) {\n node.remote.itemsMap[itemName] = true;\n }\n } else {\n node.remote.body = bodyOrItemsMap;\n node.remote.contentType = contentType;\n\n parentNode = nodes[parentPath];\n if (parentNode && parentNode.local && parentNode.local.itemsMap) {\n itemName = path.substring(parentPath.length);\n parentNode.local.itemsMap[itemName] = true;\n if (equal(parentNode.local.itemsMap, parentNode.common.itemsMap)) {\n delete parentNode.local;\n }\n }\n }\n\n nodes[path] = this.autoMerge(node);\n return {\n toBeSaved: nodes,\n missingChildren: missingChildren\n };\n });\n }\n\n completePush (path, action, conflict, revision) {\n return this.rs.local.getNodes([path]).then(nodes => {\n var node = nodes[path];\n\n if (!node.push) {\n this.stopped = true;\n throw new Error('completePush called but no push version!');\n }\n\n if (conflict) {\n log('[Sync] We have a conflict');\n\n if (!node.remote || node.remote.revision !== revision) {\n node.remote = {\n revision: revision || 'conflict',\n timestamp: this.now()\n };\n delete node.push;\n }\n\n nodes[path] = this.autoMerge(node);\n } else {\n node.common = {\n revision: revision,\n timestamp: this.now()\n };\n\n if (action === 'put') {\n node.common.body = node.push.body;\n node.common.contentType = node.push.contentType;\n\n if (equal(node.local.body, node.push.body) &&\n node.local.contentType === node.push.contentType) {\n delete node.local;\n }\n\n delete node.push;\n } else if (action === 'delete') {\n if (node.local.body === false) { // No new local changes since push; flush it.\n nodes[path] = undefined;\n } else {\n delete node.push;\n }\n }\n }\n\n return this.rs.local.setNodes(this.flush(nodes));\n });\n }\n\n dealWithFailure (path) {\n return this.rs.local.getNodes([path]).then(nodes => {\n if (nodes[path]) {\n delete nodes[path].push;\n return this.rs.local.setNodes(this.flush(nodes));\n }\n });\n }\n\n interpretStatus (statusCode) {\n const status = {\n statusCode: statusCode,\n successful: undefined,\n conflict: undefined,\n unAuth: undefined,\n notFound: undefined,\n changed: undefined,\n networkProblems: undefined\n };\n\n if (statusCode === 'offline' || statusCode === 'timeout') {\n status.successful = false;\n status.networkProblems = true;\n return status;\n }\n\n let series = Math.floor(statusCode / 100);\n\n status.successful = (series === 2 ||\n statusCode === 304 ||\n statusCode === 412 ||\n statusCode === 404),\n status.conflict = (statusCode === 412);\n status.unAuth = ((statusCode === 401 && this.rs.remote.token !== Authorize.IMPLIED_FAKE_TOKEN) ||\n statusCode === 402 ||\n statusCode === 403);\n status.notFound = (statusCode === 404);\n status.changed = (statusCode !== 304);\n\n return status;\n }\n\n handleGetResponse (path, status, bodyOrItemsMap, contentType, revision) {\n if (status.notFound) {\n if (isFolder(path)) {\n bodyOrItemsMap = {};\n } else {\n bodyOrItemsMap = false;\n }\n }\n\n if (status.changed) {\n return this.completeFetch(path, bodyOrItemsMap, contentType, revision)\n .then(dataFromFetch => {\n if (isFolder(path)) {\n if (this.corruptServerItemsMap(bodyOrItemsMap)) {\n log('[Sync] WARNING: Discarding corrupt folder description from server for ' + path);\n return false;\n } else {\n return this.markChildren(path, bodyOrItemsMap, dataFromFetch.toBeSaved, dataFromFetch.missingChildren)\n .then(() => { return true; });\n }\n } else {\n return this.rs.local.setNodes(this.flush(dataFromFetch.toBeSaved))\n .then(() => { return true; });\n }\n });\n } else {\n return this.updateCommonTimestamp(path, revision)\n .then(() => { return true; });\n }\n }\n\n handleResponse (path, action, r) {\n var status = this.interpretStatus(r.statusCode);\n\n if (status.successful) {\n if (action === 'get') {\n return this.handleGetResponse(path, status, r.body, r.contentType, r.revision);\n } else if (action === 'put' || action === 'delete') {\n return this.completePush(path, action, status.conflict, r.revision).then(function () {\n return true;\n });\n } else {\n throw new Error(`cannot handle response for unknown action ${action}`);\n }\n } else {\n // Unsuccessful\n var error;\n if (status.unAuth) {\n error = new Authorize.Unauthorized();\n } else if (status.networkProblems) {\n error = new Sync.SyncError('Network request failed.');\n } else {\n error = new Error('HTTP response code ' + status.statusCode + ' received.');\n }\n\n return this.dealWithFailure(path).then(() => {\n this.rs._emit('error', error);\n throw error;\n });\n }\n }\n\n finishTask (task) {\n if (task.action === undefined) {\n delete this._running[task.path];\n return;\n }\n\n return task.promise\n .then(res => {\n return this.handleResponse(task.path, task.action, res);\n }, err => {\n log('[Sync] wireclient rejects its promise!', task.path, task.action, err);\n return this.handleResponse(task.path, task.action, { statusCode: 'offline' });\n })\n .then(completed => {\n delete this._timeStarted[task.path];\n delete this._running[task.path];\n\n if (completed) {\n if (this._tasks[task.path]) {\n for (var i=0; i < this._tasks[task.path].length; i++) {\n this._tasks[task.path][i]();\n }\n delete this._tasks[task.path];\n }\n }\n\n this.rs._emit('sync-req-done');\n\n this.collectTasks(false).then(() => {\n // See if there are any more tasks that are not refresh tasks\n if (!this.hasTasks() || this.stopped) {\n log('[Sync] Sync is done! Reschedule?', Object.getOwnPropertyNames(this._tasks).length, this.stopped);\n if (!this.done) {\n this.done = true;\n this.rs._emit('sync-done');\n }\n } else {\n // Use a 10ms timeout to let the JavaScript runtime catch its breath\n // (and hopefully force an IndexedDB auto-commit?), and also to cause\n // the threads to get staggered and get a good spread over time:\n setTimeout(() => { this.doTasks(); }, 10);\n }\n });\n }, err => {\n log('[Sync] Error', err);\n delete this._timeStarted[task.path];\n delete this._running[task.path];\n this.rs._emit('sync-req-done');\n if (!this.done) {\n this.done = true;\n this.rs._emit('sync-done');\n }\n });\n }\n\n doTasks () {\n let numToHave, numAdded = 0, numToAdd, path;\n if (this.rs.remote.connected) {\n if (this.rs.remote.online) {\n numToHave = this.numThreads;\n } else {\n numToHave = 1;\n }\n } else {\n numToHave = 0;\n }\n numToAdd = numToHave - Object.getOwnPropertyNames(this._running).length;\n if (numToAdd <= 0) {\n return true;\n }\n for (path in this._tasks) {\n if (!this._running[path]) {\n this._timeStarted[path] = this.now();\n this._running[path] = this.doTask(path);\n this._running[path].then(this.finishTask.bind(this));\n numAdded++;\n if (numAdded >= numToAdd) {\n return true;\n }\n }\n }\n return (numAdded >= numToAdd);\n }\n\n collectTasks (alsoCheckRefresh) {\n if (this.hasTasks() || this.stopped) {\n return Promise.resolve();\n }\n\n return this.collectDiffTasks().then(numDiffs => {\n if (numDiffs || alsoCheckRefresh === false) {\n return Promise.resolve();\n } else {\n return this.collectRefreshTasks();\n }\n }, function (err) { throw err; });\n }\n\n addTask (path, cb) {\n if (!this._tasks[path]) {\n this._tasks[path] = [];\n }\n if (typeof(cb) === 'function') {\n this._tasks[path].push(cb);\n }\n }\n\n /**\n * Method: sync\n **/\n sync () {\n this.done = false;\n\n if (!this.doTasks()) {\n return this.collectTasks().then(() => {\n try {\n this.doTasks();\n } catch(e) {\n log('[Sync] doTasks error', e);\n }\n }, function (e) {\n log('[Sync] Sync error', e);\n throw new Error('Local cache unavailable');\n });\n } else {\n return Promise.resolve();\n }\n }\n\n static _rs_init (remoteStorage) {\n syncCycleCb = function () {\n // if (!config.cache) return false\n log('[Sync] syncCycleCb calling syncCycle');\n if (Env.isBrowser()) { handleVisibility(remoteStorage); }\n\n if (!remoteStorage.sync) {\n // Call this now that all other modules are also ready:\n remoteStorage.sync = new Sync(remoteStorage);\n\n if (remoteStorage.syncStopped) {\n log('[Sync] Instantiating sync stopped');\n remoteStorage.sync.stopped = true;\n delete remoteStorage.syncStopped;\n }\n }\n\n log('[Sync] syncCycleCb calling syncCycle');\n remoteStorage.syncCycle();\n };\n\n syncOnConnect = function() {\n remoteStorage.removeEventListener('connected', syncOnConnect);\n remoteStorage.startSync();\n };\n\n remoteStorage.on('ready', syncCycleCb);\n remoteStorage.on('connected', syncOnConnect);\n }\n\n static _rs_cleanup (remoteStorage) {\n remoteStorage.stopSync();\n remoteStorage.removeEventListener('ready', syncCycleCb);\n remoteStorage.removeEventListener('connected', syncOnConnect);\n\n remoteStorage.sync = undefined;\n delete remoteStorage.sync;\n }\n\n}\n\nSync.SyncError = class extends Error {\n constructor (originalError) {\n super();\n this.name = 'SyncError';\n let msg = 'Sync failed: ';\n if (typeof(originalError) === 'object' && 'message' in originalError) {\n msg += originalError.message;\n this.stack = originalError.stack;\n this.originalError = originalError;\n } else {\n msg += originalError;\n }\n this.message = msg;\n }\n};\n\nmodule.exports = Sync;\n","const util = require('./util');\nconst config = require('./config');\nconst log = require('./log');\n\n/**\n * This module defines functions that are mixed into remoteStorage.local when\n * it is instantiated (currently one of indexeddb.js, localstorage.js, or\n * inmemorystorage.js).\n *\n * All remoteStorage.local implementations should therefore implement\n * this.getNodes, this.setNodes, and this.forAllNodes. The rest is blended in\n * here to create a GPD (get/put/delete) interface which the BaseClient can\n * talk to.\n *\n * @interface\n *\n */\n\nconst isFolder = util.isFolder;\nconst isDocument = util.isDocument;\nconst deepClone = util.deepClone;\n\nfunction getLatest(node) {\n if (typeof(node) !== 'object' || typeof(node.path) !== 'string') {\n return;\n }\n if (isFolder(node.path)) {\n if (node.local && node.local.itemsMap) {\n return node.local;\n }\n if (node.common && node.common.itemsMap) {\n return node.common;\n }\n } else {\n if (node.local) {\n if (node.local.body && node.local.contentType) {\n return node.local;\n }\n if (node.local.body === false) {\n return;\n }\n }\n if (node.common && node.common.body && node.common.contentType) {\n return node.common;\n }\n // Migration code! Once all apps use at least this version of the lib, we\n // can publish clean-up code that migrates over any old-format data, and\n // stop supporting it. For now, new apps will support data in both\n // formats, thanks to this:\n if (node.body && node.contentType) {\n return {\n body: node.body,\n contentType: node.contentType\n };\n }\n }\n}\n\nfunction isOutdated(nodes, maxAge) {\n var path;\n for (path in nodes) {\n if (nodes[path] && nodes[path].remote) {\n return true;\n }\n var nodeVersion = getLatest(nodes[path]);\n if (nodeVersion && nodeVersion.timestamp && (new Date().getTime()) - nodeVersion.timestamp <= maxAge) {\n return false;\n } else if (!nodeVersion) {\n return true;\n }\n }\n return true;\n}\n\nvar pathsFromRoot = util.pathsFromRoot;\n\nfunction makeNode(path) {\n var node = { path: path, common: { } };\n\n if (isFolder(path)) {\n node.common.itemsMap = {};\n }\n return node;\n}\n\nfunction updateFolderNodeWithItemName(node, itemName) {\n if (!node.common) {\n node.common = {\n itemsMap: {}\n };\n }\n if (!node.common.itemsMap) {\n node.common.itemsMap = {};\n }\n if (!node.local) {\n node.local = deepClone(node.common);\n }\n if (!node.local.itemsMap) {\n node.local.itemsMap = node.common.itemsMap;\n }\n node.local.itemsMap[itemName] = true;\n\n return node;\n}\n\nvar methods = {\n\n // TODO: improve our code structure so that this function\n // could call sync.queueGetRequest directly instead of needing\n // this hacky third parameter as a callback\n get: function (path, maxAge, queueGetRequest) {\n var self = this;\n if (typeof(maxAge) === 'number') {\n return self.getNodes(pathsFromRoot(path))\n .then(function (objs) {\n var node = getLatest(objs[path]);\n if (isOutdated(objs, maxAge)) {\n return queueGetRequest(path);\n } else if (node) {\n return {statusCode: 200, body: node.body || node.itemsMap, contentType: node.contentType};\n } else {\n return {statusCode: 404};\n }\n });\n } else {\n return self.getNodes([path])\n .then(function (objs) {\n var node = getLatest(objs[path]);\n if (node) {\n if (isFolder(path)) {\n for (var i in node.itemsMap) {\n // the hasOwnProperty check here is only because our jshint settings require it:\n if (node.itemsMap.hasOwnProperty(i) && node.itemsMap[i] === false) {\n delete node.itemsMap[i];\n }\n }\n }\n return {statusCode: 200, body: node.body || node.itemsMap, contentType: node.contentType};\n } else {\n return {statusCode: 404};\n }\n });\n }\n },\n\n put: function (path, body, contentType) {\n const paths = pathsFromRoot(path);\n\n function _processNodes(nodePaths, nodes) {\n try {\n for (var i = 0, len = nodePaths.length; i < len; i++) {\n const nodePath = nodePaths[i];\n let node = nodes[nodePath];\n let previous;\n\n if (!node) {\n nodes[nodePath] = node = makeNode(nodePath);\n }\n\n // Document\n if (i === 0) {\n previous = getLatest(node);\n node.local = {\n body: body,\n contentType: contentType,\n previousBody: (previous ? previous.body : undefined),\n previousContentType: (previous ? previous.contentType : undefined),\n };\n }\n // Folder\n else {\n var itemName = nodePaths[i-1].substring(nodePath.length);\n node = updateFolderNodeWithItemName(node, itemName);\n }\n }\n return nodes;\n } catch (e) {\n log('[Cachinglayer] Error during PUT', nodes, e);\n throw e;\n }\n }\n\n return this._updateNodes(paths, _processNodes);\n },\n\n delete: function (path) {\n const paths = pathsFromRoot(path);\n\n return this._updateNodes(paths, function (nodePaths, nodes) {\n for (var i = 0, len = nodePaths.length; i < len; i++) {\n const nodePath = nodePaths[i];\n const node = nodes[nodePath];\n let previous;\n\n if (!node) {\n console.error('Cannot delete non-existing node ' + nodePath);\n continue;\n }\n\n if (i === 0) {\n // Document\n previous = getLatest(node);\n node.local = {\n body: false,\n previousBody: (previous ? previous.body : undefined),\n previousContentType: (previous ? previous.contentType : undefined),\n };\n } else {\n // Folder\n if (!node.local) {\n node.local = deepClone(node.common);\n }\n var itemName = nodePaths[i-1].substring(nodePath.length);\n delete node.local.itemsMap[itemName];\n\n if (Object.getOwnPropertyNames(node.local.itemsMap).length > 0) {\n // This folder still contains other items, don't remove any further ancestors\n break;\n }\n }\n }\n return nodes;\n });\n },\n\n flush: function (path) {\n var self = this;\n return self._getAllDescendentPaths(path).then(function (paths) {\n return self.getNodes(paths);\n }).then(function (nodes) {\n for (var nodePath in nodes) {\n const node = nodes[nodePath];\n\n if (node && node.common && node.local) {\n self._emitChange({\n path: node.path,\n origin: 'local',\n oldValue: (node.local.body === false ? undefined : node.local.body),\n newValue: (node.common.body === false ? undefined : node.common.body)\n });\n }\n nodes[nodePath] = undefined;\n }\n\n return self.setNodes(nodes);\n });\n },\n\n _emitChange: function (obj) {\n if (config.changeEvents[obj.origin]) {\n this._emit('change', obj);\n }\n },\n\n fireInitial: function () {\n if (!config.changeEvents.local) {\n return;\n }\n var self = this;\n self.forAllNodes(function (node) {\n var latest;\n if (isDocument(node.path)) {\n latest = getLatest(node);\n if (latest) {\n self._emitChange({\n path: node.path,\n origin: 'local',\n oldValue: undefined,\n oldContentType: undefined,\n newValue: latest.body,\n newContentType: latest.contentType\n });\n }\n }\n }).then(function () {\n self._emit('local-events-done');\n });\n },\n\n onDiff: function (diffHandler) {\n this.diffHandler = diffHandler;\n },\n\n migrate: function (node) {\n if (typeof(node) === 'object' && !node.common) {\n node.common = {};\n if (typeof(node.path) === 'string') {\n if (node.path.substr(-1) === '/' && typeof(node.body) === 'object') {\n node.common.itemsMap = node.body;\n }\n } else {\n //save legacy content of document node as local version\n if (!node.local) {\n node.local = {};\n }\n node.local.body = node.body;\n node.local.contentType = node.contentType;\n }\n }\n return node;\n },\n\n // FIXME\n // this process of updating nodes needs to be heavily documented first, then\n // refactored. Right now it's almost impossible to refactor as there's no\n // explanation of why things are implemented certain ways or what the goal(s)\n // of the behavior are. -slvrbckt (+1 -les)\n _updateNodesRunning: false,\n _updateNodesQueued: [],\n _updateNodes: function (paths, _processNodes) {\n return new Promise(function(resolve, reject) {\n this._doUpdateNodes(paths, _processNodes, {\n resolve: resolve,\n reject: reject\n });\n }.bind(this));\n },\n _doUpdateNodes: function (paths, _processNodes, promise) {\n var self = this;\n\n if (self._updateNodesRunning) {\n self._updateNodesQueued.push({\n paths: paths,\n cb: _processNodes,\n promise: promise\n });\n return;\n } else {\n self._updateNodesRunning = true;\n }\n\n self.getNodes(paths).then(function (nodes) {\n var existingNodes = deepClone(nodes);\n var changeEvents = [];\n var node;\n var equal = util.equal;\n\n nodes = _processNodes(paths, nodes);\n\n for (var path in nodes) {\n node = nodes[path];\n if (equal(node, existingNodes[path])) {\n delete nodes[path];\n }\n else if (isDocument(path)) {\n if (\n !equal(node.local.body, node.local.previousBody) ||\n node.local.contentType !== node.local.previousContentType\n ) {\n changeEvents.push({\n path: path,\n origin: 'window',\n oldValue: node.local.previousBody,\n newValue: node.local.body === false ? undefined : node.local.body,\n oldContentType: node.local.previousContentType,\n newContentType: node.local.contentType\n });\n }\n delete node.local.previousBody;\n delete node.local.previousContentType;\n }\n }\n\n self.setNodes(nodes).then(function () {\n self._emitChangeEvents(changeEvents);\n promise.resolve({statusCode: 200});\n });\n }).then(function () {\n return Promise.resolve();\n }, function (err) {\n promise.reject(err);\n }).then(function () {\n self._updateNodesRunning = false;\n var nextJob = self._updateNodesQueued.shift();\n if (nextJob) {\n self._doUpdateNodes(nextJob.paths, nextJob.cb, nextJob.promise);\n }\n });\n },\n\n _emitChangeEvents: function (events) {\n for (var i = 0, len = events.length; i < len; i++) {\n this._emitChange(events[i]);\n if (this.diffHandler) {\n this.diffHandler(events[i].path);\n }\n }\n },\n\n _getAllDescendentPaths: function (path) {\n var self = this;\n if (isFolder(path)) {\n return self.getNodes([path]).then(function (nodes) {\n var allPaths = [path];\n var latest = getLatest(nodes[path]);\n\n var itemNames = Object.keys(latest.itemsMap);\n var calls = itemNames.map(function (itemName) {\n return self._getAllDescendentPaths(path+itemName).then(function (paths) {\n for (var i = 0, len = paths.length; i < len; i++) {\n allPaths.push(paths[i]);\n }\n });\n });\n return Promise.all(calls).then(function () {\n return allPaths;\n });\n });\n } else {\n return Promise.resolve([path]);\n }\n },\n\n _getInternals: function () {\n return {\n getLatest: getLatest,\n makeNode: makeNode,\n isOutdated: isOutdated\n };\n }\n};\n\n/**\n * Mixes common caching layer functionality into an object.\n * @param {Object} object - the object to be extended\n *\n * @example\n * var MyConstructor = function () {\n * cachingLayer(this);\n * };\n */\nvar cachingLayer = function (object) {\n for (var key in methods) {\n object[key] = methods[key];\n }\n};\n\nmodule.exports = cachingLayer;\n","'use strict';\n\nconst util = require('./util');\nconst Dropbox = require('./dropbox');\nconst GoogleDrive = require('./googledrive');\nconst Discover = require('./discover');\nconst BaseClient = require('./baseclient');\nconst config = require('./config');\nconst Authorize = require('./authorize');\nconst Sync = require('./sync');\nconst log = require('./log');\nconst Features = require('./features');\nconst globalContext = util.getGlobalContext();\nconst eventHandling = require('./eventhandling');\nconst getJSONFromLocalStorage = util.getJSONFromLocalStorage;\n\nvar hasLocalStorage;\n\n// TODO document and/or refactor (seems weird)\nfunction emitUnauthorized(r) {\n if (r.statusCode === 403 || r.statusCode === 401) {\n this._emit('error', new Authorize.Unauthorized());\n }\n return Promise.resolve(r);\n}\n\n/**\n * Constructor for the remoteStorage object.\n *\n * This class primarily contains feature detection code and convenience API.\n *\n * Depending on which features are built in, it contains different attributes\n * and functions. See the individual features for more information.\n *\n * @param {object} config - an optional configuration object\n * @class\n */\nvar RemoteStorage = function (cfg) {\n\n // Initial configuration property settings.\n if (typeof cfg === 'object') {\n util.extend(config, cfg);\n }\n\n eventHandling(this,\n 'ready', 'authing', 'connecting', 'connected', 'disconnected',\n 'not-connected', 'conflict', 'error', 'features-loaded',\n 'sync-interval-change', 'sync-req-done', 'sync-done',\n 'wire-busy', 'wire-done', 'network-offline', 'network-online'\n );\n\n /**\n * Pending get/put/delete calls\n *\n * @private\n */\n this._pending = [];\n\n /**\n * TODO: document\n *\n * @private\n */\n this._setGPD({\n get: this._pendingGPD('get'),\n put: this._pendingGPD('put'),\n delete: this._pendingGPD('delete')\n });\n\n /**\n * TODO: document\n *\n * @private\n */\n this._cleanups = [];\n\n /**\n * TODO: document\n *\n * @private\n */\n this._pathHandlers = { change: {} };\n\n /**\n * Holds OAuth app keys for Dropbox, Google Drive\n *\n * @private\n */\n this.apiKeys = {};\n\n hasLocalStorage = util.localStorageAvailable();\n\n if (hasLocalStorage) {\n this.apiKeys = getJSONFromLocalStorage('remotestorage:api-keys') || {};\n this.setBackend(localStorage.getItem('remotestorage:backend') || 'remotestorage');\n }\n\n // Keep a reference to the orginal `on` function\n var origOn = this.on;\n\n /**\n * Register an event handler. See :ref:`rs-events` for available event names.\n *\n * @param {string} eventName - Name of the event\n * @param {function} handler - Event handler\n */\n this.on = function (eventName, handler) {\n if (this._allLoaded) {\n // check if the handler should be called immediately, because the\n // event has happened already\n switch(eventName) {\n case 'features-loaded':\n setTimeout(handler, 0);\n break;\n case 'ready':\n if (this.remote) {\n setTimeout(handler, 0);\n }\n break;\n case 'connected':\n if (this.remote && this.remote.connected) {\n setTimeout(handler, 0);\n }\n break;\n case 'not-connected':\n if (this.remote && !this.remote.connected) {\n setTimeout(handler, 0);\n }\n break;\n }\n }\n\n return origOn.call(this, eventName, handler);\n };\n\n // load all features and emit `ready`\n this._init();\n\n /**\n * TODO: document\n */\n this.fireInitial = function () {\n if (this.local) {\n setTimeout(this.local.fireInitial.bind(this.local), 0);\n }\n }.bind(this);\n\n this.on('ready', this.fireInitial.bind(this));\n this.loadModules();\n};\n\n// FIXME: Instead of doing this, would be better to only\n// export setAuthURL / getAuthURL from RemoteStorage prototype\nRemoteStorage.Authorize = Authorize;\n\nRemoteStorage.SyncError = Sync.SyncError;\nRemoteStorage.Unauthorized = Authorize.Unauthorized;\nRemoteStorage.DiscoveryError = Discover.DiscoveryError;\n\nRemoteStorage.prototype = {\n\n /**\n * Load all modules passed as arguments\n *\n * @private\n */\n loadModules: function loadModules() {\n config.modules.forEach(this.addModule.bind(this));\n },\n\n /**\n * Initiate the OAuth authorization flow.\n *\n * This function is called by custom storage backend implementations\n * (e.g. Dropbox or Google Drive).\n *\n * @param {object} options\n * @param {string} options.authURL - URL of the authorization endpoint\n * @param {string} [options.scope] - access scope\n * @param {string} [options.clientId] - client identifier (defaults to the\n * origin of the redirectUri)\n *\n * @private\n */\n authorize: function authorize (options) {\n this.access.setStorageType(this.remote.storageApi);\n if (typeof options.scope === 'undefined') {\n options.scope = this.access.scopeParameter;\n }\n\n options.redirectUri = globalContext.cordova ? config.cordovaRedirectUri : String(Authorize.getLocation());\n\n if (typeof options.clientId === 'undefined') {\n options.clientId = options.redirectUri.match(/^(https?:\\/\\/[^/]+)/)[0];\n }\n\n Authorize(this, options);\n },\n\n /**\n * TODO: document\n *\n * @private\n */\n impliedauth: function (storageApi, redirectUri) {\n storageApi = this.remote.storageApi;\n redirectUri = String(document.location);\n\n log('ImpliedAuth proceeding due to absent authURL; storageApi = ' + storageApi + ' redirectUri = ' + redirectUri);\n // Set a fixed access token, signalling to not send it as Bearer\n this.remote.configure({\n token: Authorize.IMPLIED_FAKE_TOKEN\n });\n document.location = redirectUri;\n },\n\n /**\n * @property {object} remote\n *\n * Depending on the chosen backend, this is either an instance of ``WireClient``,\n * ``Dropbox`` or ``GoogleDrive``.\n *\n * @property {boolean} remote.connected - Whether or not a remote store is connected\n * @property {boolean} remote.online - Whether last sync action was successful or not\n * @property {string} remote.userAddress - The user address of the connected user\n * @property {string} remote.properties - The properties of the WebFinger link\n */\n\n /**\n * Connect to a remoteStorage server.\n *\n * Discovers the WebFinger profile of the given user address and initiates\n * the OAuth dance.\n *\n * This method must be called *after* all required access has been claimed.\n * When using the connect widget, it will call this method itself.\n *\n * Special cases:\n *\n * 1. If a bearer token is supplied as second argument, the OAuth dance\n * will be skipped and the supplied token be used instead. This is\n * useful outside of browser environments, where the token has been\n * acquired in a different way.\n *\n * 2. If the Webfinger profile for the given user address doesn't contain\n * an auth URL, the library will assume that client and server have\n * established authorization among themselves, which will omit bearer\n * tokens in all requests later on. This is useful for example when using\n * Kerberos and similar protocols.\n *\n * @param {string} userAddress - The user address (user@host) to connect to.\n * @param {string} token - (optional) A bearer token acquired beforehand\n */\n connect: function (userAddress, token) {\n this.setBackend('remotestorage');\n if (userAddress.indexOf('@') < 0) {\n this._emit('error', new RemoteStorage.DiscoveryError(\"User address doesn't contain an @.\"));\n return;\n }\n\n if (globalContext.cordova) {\n if (typeof config.cordovaRedirectUri !== 'string') {\n this._emit('error', new RemoteStorage.DiscoveryError(\"Please supply a custom HTTPS redirect URI for your Cordova app\"));\n return;\n }\n if (!globalContext.cordova.InAppBrowser) {\n this._emit('error', new RemoteStorage.DiscoveryError(\"Please include the InAppBrowser Cordova plugin to enable OAuth\"));\n return;\n }\n }\n\n this.remote.configure({\n userAddress: userAddress\n });\n this._emit('connecting');\n\n var discoveryTimeout = setTimeout(function () {\n this._emit('error', new RemoteStorage.DiscoveryError(\"No storage information found for this user address.\"));\n }.bind(this), config.discoveryTimeout);\n\n Discover(userAddress).then(info => {\n // Info contains fields: href, storageApi, authURL (optional), properties\n\n clearTimeout(discoveryTimeout);\n this._emit('authing');\n info.userAddress = userAddress;\n this.remote.configure(info);\n if (! this.remote.connected) {\n if (info.authURL) {\n if (typeof token === 'undefined') {\n // Normal authorization step; the default way to connect\n this.authorize({ authURL: info.authURL });\n } else if (typeof token === 'string') {\n // Token supplied directly by app/developer/user\n log('Skipping authorization sequence and connecting with known token');\n this.remote.configure({ token: token });\n } else {\n throw new Error(\"Supplied bearer token must be a string\");\n }\n } else {\n // In lieu of an excplicit authURL, assume that the browser and\n // server handle any authorization needs; for instance, TLS may\n // trigger the browser to use a client certificate, or a 401 Not\n // Authorized response may make the browser send a Kerberos ticket\n // using the SPNEGO method.\n this.impliedauth();\n }\n }\n }, (/*err*/) => {\n clearTimeout(discoveryTimeout);\n this._emit('error', new RemoteStorage.DiscoveryError(\"No storage information found for this user address.\"));\n });\n },\n\n /**\n * Reconnect the remote server to get a new authorization.\n */\n reconnect: function () {\n this.remote.configure({ token: null });\n\n if (this.backend === 'remotestorage') {\n this.connect(this.remote.userAddress);\n } else {\n this.remote.connect();\n }\n },\n\n /**\n * \"Disconnect\" from remote server to terminate current session.\n *\n * This method clears all stored settings and deletes the entire local\n * cache.\n */\n disconnect: function () {\n if (this.remote) {\n this.remote.configure({\n userAddress: null,\n href: null,\n storageApi: null,\n token: null,\n properties: null\n });\n }\n this._setGPD({\n get: this._pendingGPD('get'),\n put: this._pendingGPD('put'),\n delete: this._pendingGPD('delete')\n });\n var n = this._cleanups.length, i = 0;\n\n var oneDone = function () {\n i++;\n if (i >= n) {\n this._init();\n log('Done cleaning up, emitting disconnected and disconnect events');\n this._emit('disconnected');\n }\n }.bind(this);\n\n if (n > 0) {\n this._cleanups.forEach(function (cleanup) {\n var cleanupResult = cleanup(this);\n if (typeof(cleanupResult) === 'object' && typeof(cleanupResult.then) === 'function') {\n cleanupResult.then(oneDone);\n } else {\n oneDone();\n }\n }.bind(this));\n } else {\n oneDone();\n }\n },\n\n /**\n * TODO: document\n *\n * @private\n */\n setBackend: function (what) {\n this.backend = what;\n if (hasLocalStorage) {\n if (what) {\n localStorage.setItem('remotestorage:backend', what);\n } else {\n localStorage.removeItem('remotestorage:backend');\n }\n }\n },\n\n /**\n * Add a \"change\" event handler to the given path. Whenever a \"change\"\n * happens (as determined by the backend, such as e.g.\n * ) and the affected path is equal to or below the\n * given 'path', the given handler is called.\n *\n * You should usually not use this method directly, but instead use the\n * \"change\" events provided by :doc:`BaseClient `\n *\n * @param {string} path - Absolute path to attach handler to\n * @param {function} handler - Handler function\n */\n onChange: function (path, handler) {\n if (! this._pathHandlers.change[path]) {\n this._pathHandlers.change[path] = [];\n }\n this._pathHandlers.change[path].push(handler);\n },\n\n /**\n * TODO: do we still need this, now that we always instantiate the prototype?\n *\n * Enable remoteStorage logging.\n */\n enableLog: function () {\n config.logging = true;\n },\n\n /**\n * TODO: do we still need this, now that we always instantiate the prototype?\n *\n * Disable remoteStorage logging\n */\n disableLog: function () {\n config.logging = false;\n },\n\n /**\n * log\n *\n * The same as .\n */\n log: function () {\n log.apply(RemoteStorage, arguments);\n },\n\n /**\n * Set the OAuth key/ID for either GoogleDrive or Dropbox backend support.\n *\n * @param {Object} apiKeys - A config object with these properties:\n * @param {string} [apiKeys.type] - Backend type: 'googledrive' or 'dropbox'\n * @param {string} [apiKeys.key] - Client ID for GoogleDrive, or app key for Dropbox\n */\n setApiKeys: function (apiKeys) {\n const validTypes = ['googledrive', 'dropbox'];\n if (typeof apiKeys !== 'object' || !Object.keys(apiKeys).every(type => validTypes.includes(type))) {\n console.error('setApiKeys() was called with invalid arguments') ;\n return false;\n }\n\n Object.keys(apiKeys).forEach(type => {\n let key = apiKeys[type];\n if (!key) { delete this.apiKeys[type]; return; }\n\n switch(type) {\n case 'dropbox':\n this.apiKeys['dropbox'] = { appKey: key };\n if (typeof this.dropbox === 'undefined' ||\n this.dropbox.clientId !== key) {\n Dropbox._rs_init(this);\n }\n break;\n case 'googledrive':\n this.apiKeys['googledrive'] = { clientId: key };\n if (typeof this.googledrive === 'undefined' ||\n this.googledrive.clientId !== key) {\n GoogleDrive._rs_init(this);\n }\n break;\n }\n return true;\n });\n\n if (hasLocalStorage) {\n localStorage.setItem('remotestorage:api-keys', JSON.stringify(this.apiKeys));\n }\n },\n\n /**\n * Set redirect URI to be used for the OAuth redirect within the\n * in-app-browser window in Cordova apps.\n *\n * @param {string} uri - A valid HTTP(S) URI\n */\n setCordovaRedirectUri: function (uri) {\n if (typeof uri !== 'string' || !uri.match(/http(s)?:\\/\\//)) {\n throw new Error(\"Cordova redirect URI must be a URI string\");\n }\n config.cordovaRedirectUri = uri;\n },\n\n\n //\n // FEATURES INITIALIZATION\n //\n\n _init: Features.loadFeatures,\n features: Features.features,\n loadFeature: Features.loadFeature,\n featureSupported: Features.featureSupported,\n featureDone: Features.featureDone,\n featuresDone: Features.featuresDone,\n featuresLoaded: Features.featuresLoaded,\n featureInitialized: Features.featureInitialized,\n featureFailed: Features.featureFailed,\n hasFeature: Features.hasFeature,\n _setCachingModule: Features._setCachingModule,\n _collectCleanupFunctions: Features._collectCleanupFunctions,\n _fireReady: Features._fireReady,\n initFeature: Features.initFeature,\n\n //\n // GET/PUT/DELETE INTERFACE HELPERS\n //\n\n /**\n * TODO: document\n *\n * @private\n */\n _setGPD: function (impl, context) {\n function wrap(func) {\n return function () {\n return func.apply(context, arguments)\n .then(emitUnauthorized.bind(this));\n };\n }\n this.get = wrap(impl.get);\n this.put = wrap(impl.put);\n this.delete = wrap(impl.delete);\n },\n\n /**\n * TODO: document\n *\n * @private\n */\n _pendingGPD: function (methodName) {\n return function () {\n var methodArguments = Array.prototype.slice.call(arguments);\n return new Promise(function(resolve, reject) {\n this._pending.push({\n method: methodName,\n args: methodArguments,\n promise: {\n resolve: resolve,\n reject: reject\n }\n });\n }.bind(this));\n }.bind(this);\n },\n\n /**\n * TODO: document\n *\n * @private\n */\n _processPending: function () {\n this._pending.forEach(function (pending) {\n try {\n this[pending.method].apply(this, pending.args).then(pending.promise.resolve, pending.promise.reject);\n } catch(e) {\n pending.promise.reject(e);\n }\n }.bind(this));\n this._pending = [];\n },\n\n //\n // CHANGE EVENT HANDLING\n //\n\n /**\n * TODO: document\n *\n * @private\n */\n _bindChange: function (object) {\n object.on('change', this._dispatchEvent.bind(this, 'change'));\n },\n\n /**\n * TODO: document\n *\n * @private\n */\n _dispatchEvent: function (eventName, event) {\n var self = this;\n Object.keys(this._pathHandlers[eventName]).forEach(function (path) {\n var pl = path.length;\n if (event.path.substr(0, pl) === path) {\n self._pathHandlers[eventName][path].forEach(function (handler) {\n var ev = {};\n for (var key in event) { ev[key] = event[key]; }\n ev.relativePath = event.path.replace(new RegExp('^' + path), '');\n try {\n handler(ev);\n } catch(e) {\n console.error(\"'change' handler failed: \", e, e.stack);\n self._emit('error', e);\n }\n });\n }\n });\n },\n\n /**\n * This method enables you to quickly instantiate a BaseClient, which you can\n * use to directly read and manipulate data in the connected storage account.\n *\n * Please use this method only for debugging and development, and choose or\n * create a :doc:`data module ` for your app to use.\n *\n * @param {string} path - The base directory of the BaseClient that will be\n * returned (with a leading and a trailing slash)\n *\n * @returns {BaseClient} A client with the specified scope (category/base directory)\n */\n scope: function (path) {\n if (typeof(path) !== 'string') {\n throw 'Argument \\'path\\' of baseClient.scope must be a string';\n }\n\n if (!this.access.checkPathPermission(path, 'r')) {\n var escapedPath = path.replace(/(['\\\\])/g, '\\\\$1');\n console.warn('WARNING: please call remoteStorage.access.claim(\\'' + escapedPath + '\\', \\'r\\') (read only) or remoteStorage.access.claim(\\'' + escapedPath + '\\', \\'rw\\') (read/write) first');\n }\n return new BaseClient(this, path);\n },\n\n\n /**\n * Get the value of the sync interval when application is in the foreground\n *\n * @returns {number} A number of milliseconds\n */\n getSyncInterval: function () {\n return config.syncInterval;\n },\n\n /**\n * Set the value of the sync interval when application is in the foreground\n *\n * @param {number} interval - Sync interval in milliseconds (between 1000 and 3600000)\n */\n setSyncInterval: function (interval) {\n if (!isValidInterval(interval)) {\n throw interval + \" is not a valid sync interval\";\n }\n var oldValue = config.syncInterval;\n config.syncInterval = parseInt(interval, 10);\n this._emit('sync-interval-change', {oldValue: oldValue, newValue: interval});\n },\n\n /**\n * Get the value of the sync interval when application is in the background\n *\n * @returns {number} A number of milliseconds\n */\n getBackgroundSyncInterval: function () {\n return config.backgroundSyncInterval;\n },\n\n /**\n * Set the value of the sync interval when the application is in the\n * background\n *\n * @param interval - Sync interval in milliseconds (between 1000 and 3600000)\n */\n setBackgroundSyncInterval: function (interval) {\n if(!isValidInterval(interval)) {\n throw interval + \" is not a valid sync interval\";\n }\n var oldValue = config.backgroundSyncInterval;\n config.backgroundSyncInterval = parseInt(interval, 10);\n this._emit('sync-interval-change', {oldValue: oldValue, newValue: interval});\n },\n\n /**\n * Get the value of the current sync interval. Can be background or\n * foreground, custom or default.\n *\n * @returns {number} A number of milliseconds\n */\n getCurrentSyncInterval: function () {\n return config.isBackground ? config.backgroundSyncInterval : config.syncInterval;\n },\n\n /**\n * Get the value of the current network request timeout\n *\n * @returns {number} A number of milliseconds\n */\n getRequestTimeout: function () {\n return config.requestTimeout;\n },\n\n /**\n * Set the timeout for network requests.\n *\n * @param timeout - Timeout in milliseconds\n */\n setRequestTimeout: function (timeout) {\n config.requestTimeout = parseInt(timeout, 10);\n },\n\n /**\n * TODO: document\n *\n * @private\n */\n syncCycle: function () {\n if (!this.sync || this.sync.stopped) {\n return;\n }\n\n this.on('sync-done', function () {\n log('[Sync] Sync done. Setting timer to', this.getCurrentSyncInterval());\n if (this.sync && !this.sync.stopped) {\n if (this._syncTimer) {\n clearTimeout(this._syncTimer);\n this._syncTimer = undefined;\n }\n this._syncTimer = setTimeout(this.sync.sync.bind(this.sync), this.getCurrentSyncInterval());\n }\n }.bind(this));\n\n this.sync.sync();\n },\n\n /**\n * Start synchronization with remote storage, downloading and uploading any\n * changes within the cached paths.\n *\n * Please consider: local changes will attempt sync immediately, and remote\n * changes should also be synced timely when using library defaults. So\n * this is mostly useful for letting users sync manually, when pressing a\n * sync button for example. This might feel safer to them sometimes, esp.\n * when shifting between offline and online a lot.\n *\n * @returns {Promise} A Promise which resolves when the sync has finished\n */\n startSync: function () {\n if (!config.cache) {\n console.warn('Nothing to sync, because caching is disabled.');\n return Promise.resolve();\n }\n this.sync.stopped = false;\n this.syncStopped = false;\n return this.sync.sync();\n },\n\n /**\n * Stop the periodic synchronization.\n */\n stopSync: function () {\n clearTimeout(this._syncTimer);\n this._syncTimer = undefined;\n\n if (this.sync) {\n log('[Sync] Stopping sync');\n this.sync.stopped = true;\n } else {\n // The sync class has not been initialized yet, so we make sure it will\n // not start the syncing process as soon as it's initialized.\n log('[Sync] Will instantiate sync stopped');\n this.syncStopped = true;\n }\n }\n\n};\n\n\n/**\n* Check if interval is valid: numeric and between 1000ms and 3600000ms\n*\n* @private\n*/\nfunction isValidInterval(interval) {\n return (typeof interval === 'number' &&\n interval > 1000 &&\n interval < 3600000);\n}\n\nRemoteStorage.util = util;\n\n/**\n * @property connected\n *\n * Boolean property indicating if remoteStorage is currently connected.\n */\nObject.defineProperty(RemoteStorage.prototype, 'connected', {\n get: function () {\n return this.remote.connected;\n }\n});\n\n/**\n * @property access\n *\n * Tracking claimed access scopes. A instance.\n*/\nvar Access = require('./access');\nObject.defineProperty(RemoteStorage.prototype, 'access', {\n get: function() {\n var access = new Access();\n Object.defineProperty(this, 'access', {\n value: access\n });\n return access;\n },\n configurable: true\n});\n\n// TODO Clean up/harmonize how modules are loaded and/or document this architecture properly\n//\n// At this point the remoteStorage object has not been created yet.\n// Only its prototype exists so far, so we define a self-constructing\n// property on there:\n\n/**\n * Property: caching\n *\n * Caching settings. A instance.\n */\n\n// FIXME Was in rs_init of Caching but don't want to require RemoteStorage from there.\nvar Caching = require('./caching');\nObject.defineProperty(RemoteStorage.prototype, 'caching', {\n configurable: true,\n get: function () {\n var caching = new Caching();\n Object.defineProperty(this, 'caching', {\n value: caching\n });\n return caching;\n }\n});\n\n/*\n * @property local\n *\n * Access to the local caching backend used. Usually either a\n * or instance.\n *\n * Not available, when caching is turned off.\n */\n\nmodule.exports = RemoteStorage;\nrequire('./modules');\n","var g;\n\n// This works in non-strict mode\ng = (function() {\n\treturn this;\n})();\n\ntry {\n\t// This works if eval is allowed (see CSP)\n\tg = g || new Function(\"return this\")();\n} catch (e) {\n\t// This works if the window reference is available\n\tif (typeof window === \"object\") g = window;\n}\n\n// g can still be undefined, but nothing to do about it...\n// We return undefined, instead of nothing here, so it's\n// easier to handle this case. if(!global) { ...}\n\nmodule.exports = g;\n","var Authorize = require('./authorize');\nvar BaseClient = require('./baseclient');\nvar WireClient = require('./wireclient');\nvar util = require('./util');\nvar eventHandling = require('./eventhandling');\nvar RevisionCache = require('./revisioncache');\nvar Sync = require('./sync');\n\n/**\n * WORK IN PROGRESS, NOT RECOMMENDED FOR PRODUCTION USE\n *\n * Dropbox backend for RemoteStorage.js\n * This file exposes a get/put/delete interface which is compatible with\n * .\n *\n * When remoteStorage.backend is set to 'dropbox', this backend will\n * initialize and replace remoteStorage.remote with remoteStorage.dropbox.\n *\n * In order to ensure compatibility with the public folder, \n * gets hijacked to return the Dropbox public share URL.\n *\n * To use this backend, you need to specify the Dropbox app key like so:\n *\n * @example\n * remoteStorage.setApiKeys({\n * dropbox: 'your-app-key'\n * });\n *\n * An app key can be obtained by registering your app at https://www.dropbox.com/developers/apps\n *\n * Known issues:\n *\n * - Storing files larger than 150MB is not yet supported\n * - Listing and deleting folders with more than 10'000 files will cause problems\n * - Content-Type is not fully supported due to limitations of the Dropbox API\n * - Dropbox preserves cases but is not case-sensitive\n * - getItemURL is asynchronous which means it returns useful values\n * after the syncCycle\n */\n\nlet hasLocalStorage;\nconst AUTH_URL = 'https://www.dropbox.com/oauth2/authorize';\nconst SETTINGS_KEY = 'remotestorage:dropbox';\nconst PATH_PREFIX = '/remotestorage';\n\nconst isFolder = util.isFolder;\nconst cleanPath = util.cleanPath;\nconst shouldBeTreatedAsBinary = util.shouldBeTreatedAsBinary;\nconst getJSONFromLocalStorage = util.getJSONFromLocalStorage;\nconst getTextFromArrayBuffer = util.getTextFromArrayBuffer;\n\n/**\n * Map a local path to a path in Dropbox.\n *\n * @param {string} path - Path\n * @returns {string} Actual path in Dropbox\n *\n * @private\n */\nvar getDropboxPath = function (path) {\n return cleanPath(PATH_PREFIX + '/' + path).replace(/\\/$/, '');\n};\n\nvar compareApiError = function (response, expect) {\n return new RegExp('^' + expect.join('\\\\/') + '(\\\\/|$)').test(response.error_summary);\n};\n\nconst isBinaryData = function (data) {\n return data instanceof ArrayBuffer || WireClient.isArrayBufferView(data);\n};\n\n/**\n * @class\n */\nvar Dropbox = function (rs) {\n\n this.rs = rs;\n this.connected = false;\n this.rs = rs;\n this._initialFetchDone = false;\n\n eventHandling(this, 'connected', 'not-connected');\n\n this.clientId = rs.apiKeys.dropbox.appKey;\n this._revCache = new RevisionCache('rev');\n this._fetchDeltaCursor = null;\n this._fetchDeltaPromise = null;\n this._itemRefs = {};\n\n hasLocalStorage = util.localStorageAvailable();\n\n if (hasLocalStorage){\n const settings = getJSONFromLocalStorage(SETTINGS_KEY);\n if (settings) {\n this.configure(settings);\n }\n this._itemRefs = getJSONFromLocalStorage(`${SETTINGS_KEY}:shares`) || {};\n }\n if (this.connected) {\n setTimeout(this._emit.bind(this), 0, 'connected');\n }\n};\n\nDropbox.prototype = {\n online: true,\n\n /**\n * Set the backed to 'dropbox' and start the authentication flow in order\n * to obtain an API token from Dropbox.\n */\n connect: function () {\n // TODO handling when token is already present\n this.rs.setBackend('dropbox');\n if (this.token){\n hookIt(this.rs);\n } else {\n this.rs.authorize({ authURL: AUTH_URL, scope: '', clientId: this.clientId });\n }\n },\n\n /**\n * Sets the connected flag\n * Accepts its parameters according to the .\n * @param {Object} settings\n * @param {string} [settings.userAddress] - The user's email address\n * @param {string} [settings.token] - Authorization token\n *\n * @protected\n **/\n configure: function (settings) {\n // We only update this.userAddress if settings.userAddress is set to a string or to null:\n if (typeof settings.userAddress !== 'undefined') { this.userAddress = settings.userAddress; }\n // Same for this.token. If only one of these two is set, we leave the other one at its existing value:\n if (typeof settings.token !== 'undefined') { this.token = settings.token; }\n\n var writeSettingsToCache = function() {\n if (hasLocalStorage) {\n localStorage.setItem(SETTINGS_KEY, JSON.stringify({\n userAddress: this.userAddress,\n token: this.token\n }));\n }\n };\n\n var handleError = function() {\n this.connected = false;\n if (hasLocalStorage) {\n localStorage.removeItem(SETTINGS_KEY);\n }\n };\n\n if (this.token) {\n this.connected = true;\n if (this.userAddress) {\n this._emit('connected');\n writeSettingsToCache.apply(this);\n } else {\n this.info().then(function (info){\n this.userAddress = info.email;\n this._emit('connected');\n writeSettingsToCache.apply(this);\n }.bind(this)).catch(function() {\n handleError.apply(this);\n this.rs._emit('error', new Error('Could not fetch user info.'));\n }.bind(this));\n }\n } else {\n handleError.apply(this);\n }\n },\n\n /**\n * Stop waiting for the token and emit not-connected\n *\n * @protected\n */\n stopWaitingForToken: function () {\n if (!this.connected) {\n this._emit('not-connected');\n }\n },\n\n /**\n * Get all items in a folder.\n *\n * @param path {string} - path of the folder to get, with leading slash\n * @return {Object}\n * statusCode - HTTP status code\n * body - array of the items found\n * contentType - 'application/json; charset=UTF-8'\n * revision - revision of the folder\n *\n * @private\n */\n _getFolder: function (path) {\n var url = 'https://api.dropboxapi.com/2/files/list_folder';\n var revCache = this._revCache;\n var self = this;\n\n var processResponse = function (resp) {\n var body, listing;\n\n if (resp.status !== 200 && resp.status !== 409) {\n return Promise.reject('Unexpected response status: ' + resp.status);\n }\n\n try {\n body = JSON.parse(resp.responseText);\n } catch (e) {\n return Promise.reject(e);\n }\n\n if (resp.status === 409) {\n if (compareApiError(body, ['path', 'not_found'])) {\n // if the folder is not found, handle it as an empty folder\n return Promise.resolve({});\n }\n\n return Promise.reject(new Error('API returned an error: ' + body.error_summary));\n }\n\n listing = body.entries.reduce(function (map, item) {\n var isDir = item['.tag'] === 'folder';\n var itemName = item.path_lower.split('/').slice(-1)[0] + (isDir ? '/' : '');\n if (isDir){\n map[itemName] = { ETag: revCache.get(path+itemName) };\n } else {\n map[itemName] = { ETag: item.rev };\n self._revCache.set(path+itemName, item.rev);\n }\n return map;\n }, {});\n\n if (body.has_more) {\n return loadNext(body.cursor).then(function (nextListing) {\n return Object.assign(listing, nextListing);\n });\n }\n\n return Promise.resolve(listing);\n };\n\n const loadNext = function (cursor) {\n const continueURL = 'https://api.dropboxapi.com/2/files/list_folder/continue';\n const params = {\n body: { cursor: cursor }\n };\n\n return self._request('POST', continueURL, params).then(processResponse);\n };\n\n return this._request('POST', url, {\n body: {\n path: getDropboxPath(path)\n }\n }).then(processResponse).then(function (listing) {\n return Promise.resolve({\n statusCode: 200,\n body: listing,\n contentType: 'application/json; charset=UTF-8',\n revision: revCache.get(path)\n });\n });\n },\n\n /**\n * Checks for the path in ``_revCache`` and decides based on that if file\n * has changed. Calls ``_getFolder`` is the path points to a folder.\n *\n * Calls ``Dropbox.share`` afterwards to fill ``_itemRefs``.\n *\n * Compatible with ``WireClient.get``\n *\n * @param path {string} - path of the folder to get, with leading slash\n * @param options {Object}\n *\n * @protected\n */\n get: function (path, options) {\n if (! this.connected) { return Promise.reject(\"not connected (path: \" + path + \")\"); }\n var url = 'https://content.dropboxapi.com/2/files/download';\n var self = this;\n\n var savedRev = this._revCache.get(path);\n if (savedRev === null) {\n // file was deleted server side\n return Promise.resolve({statusCode: 404});\n }\n if (options && options.ifNoneMatch) {\n // We must wait for local revision cache to be initialized before\n // checking if local revision is outdated\n if (! this._initialFetchDone) {\n return this.fetchDelta().then(() => {\n return this.get(path, options);\n });\n }\n \n if (savedRev && (savedRev === options.ifNoneMatch)) {\n // nothing changed.\n return Promise.resolve({statusCode: 304});\n }\n }\n\n //use _getFolder for folders\n if (path.substr(-1) === '/') {\n return this._getFolder(path, options);\n }\n\n\n var params = {\n headers: {\n 'Dropbox-API-Arg': JSON.stringify({path: getDropboxPath(path)}),\n },\n responseType: 'arraybuffer'\n };\n if (options && options.ifNoneMatch) {\n params.headers['If-None-Match'] = options.ifNoneMatch;\n }\n\n return this._request('GET', url, params).then(function (resp) {\n var status = resp.status;\n var meta, body, mime, rev;\n if (status !== 200 && status !== 409) {\n return Promise.resolve({statusCode: status});\n }\n meta = resp.getResponseHeader('Dropbox-API-Result');\n //first encode the response as text, and later check if \n //text appears to actually be binary data\n return getTextFromArrayBuffer(resp.response, 'UTF-8').then(function (responseText) {\n body = responseText;\n if (status === 409) {\n meta = body;\n }\n\n try {\n meta = JSON.parse(meta);\n } catch(e) {\n return Promise.reject(e);\n }\n\n if (status === 409) {\n if (compareApiError(meta, ['path', 'not_found'])) {\n return {statusCode: 404};\n }\n return Promise.reject(new Error('API error while downloading file (\"' + path + '\"): ' + meta.error_summary));\n }\n\n mime = resp.getResponseHeader('Content-Type');\n rev = meta.rev;\n self._revCache.set(path, rev);\n self._shareIfNeeded(path);\n\n if (shouldBeTreatedAsBinary(responseText, mime)) {\n //return unprocessed response \n body = resp.response;\n } else {\n // handling json (always try)\n try {\n body = JSON.parse(body);\n mime = 'application/json; charset=UTF-8';\n } catch(e) {\n //Failed parsing Json, assume it is something else then\n }\n }\n\n return {\n statusCode: status, \n body: body, \n contentType: mime, \n revision: rev\n }; \n }); \n });\n },\n\n /**\n * Checks for the path in ``_revCache`` and decides based on that if file\n * has changed.\n *\n * Compatible with ``WireClient``\n *\n * Calls ``Dropbox.share`` afterwards to fill ``_itemRefs``.\n *\n * @param {string} path - path of the folder to put, with leading slash\n * @param {Object} options\n * @param {string} options.ifNoneMatch - Only create of update the file if the\n * current ETag doesn't match this string\n * @returns {Promise} Resolves with an object containing the status code,\n * content-type and revision\n * @protected\n */\n put: function (path, body, contentType, options) {\n var self = this;\n\n if (!this.connected) {\n throw new Error(\"not connected (path: \" + path + \")\");\n }\n\n //check if file has changed and return 412\n var savedRev = this._revCache.get(path);\n if (options && options.ifMatch &&\n savedRev && (savedRev !== options.ifMatch)) {\n return Promise.resolve({statusCode: 412, revision: savedRev});\n }\n if (options && (options.ifNoneMatch === '*') &&\n savedRev && (savedRev !== 'rev')) {\n return Promise.resolve({statusCode: 412, revision: savedRev});\n }\n\n if ((!contentType.match(/charset=/)) && isBinaryData(body)) {\n contentType += '; charset=binary';\n }\n\n if (body.length > 150 * 1024 * 1024) {\n //https://www.dropbox.com/developers/core/docs#chunked-upload\n return Promise.reject(new Error(\"Cannot upload file larger than 150MB\"));\n }\n\n var result;\n var needsMetadata = options && (options.ifMatch || (options.ifNoneMatch === '*'));\n var uploadParams = {\n body: body,\n contentType: contentType,\n path: path\n };\n\n if (needsMetadata) {\n result = this._getMetadata(path).then(function (metadata) {\n if (options && (options.ifNoneMatch === '*') && metadata) {\n // if !!metadata === true, the file exists\n return Promise.resolve({\n statusCode: 412,\n revision: metadata.rev\n });\n }\n\n if (options && options.ifMatch && metadata && (metadata.rev !== options.ifMatch)) {\n return Promise.resolve({\n statusCode: 412,\n revision: metadata.rev\n });\n }\n\n return self._uploadSimple(uploadParams);\n });\n } else {\n result = self._uploadSimple(uploadParams);\n }\n\n return result.then(function (ret) {\n self._shareIfNeeded(path);\n return ret;\n });\n },\n\n /**\n * Checks for the path in ``_revCache`` and decides based on that if file\n * has changed.\n *\n * Compatible with ``WireClient.delete``\n *\n * Calls ``Dropbox.share`` afterwards to fill ``_itemRefs``.\n *\n * @param {string} path - path of the folder to delete, with leading slash\n * @param {Object} options\n *\n * @protected\n */\n 'delete': function (path, options) {\n if (!this.connected) {\n throw new Error(\"not connected (path: \" + path + \")\");\n }\n\n //check if file has changed and return 412\n var savedRev = this._revCache.get(path);\n if (options && options.ifMatch && savedRev && (options.ifMatch !== savedRev)) {\n return Promise.resolve({ statusCode: 412, revision: savedRev });\n }\n\n if (options && options.ifMatch) {\n return this._getMetadata(path).then((metadata) => {\n if (options && options.ifMatch && metadata && (metadata.rev !== options.ifMatch)) {\n return Promise.resolve({\n statusCode: 412,\n revision: metadata.rev\n });\n }\n\n return this._deleteSimple(path);\n });\n }\n\n return this._deleteSimple(path);\n },\n\n /**\n * Calls share, if the provided path resides in a public folder.\n *\n * @private\n */\n _shareIfNeeded: function (path) {\n if (path.match(/^\\/public\\/.*[^/]$/) && this._itemRefs[path] === undefined) {\n this.share(path);\n }\n },\n\n /**\n * Gets a publicly-accessible URL for the path from Dropbox and stores it\n * in ``_itemRefs``.\n *\n * @return {Promise} a promise for the URL\n *\n * @private\n */\n share: function (path) {\n var url = 'https://api.dropboxapi.com/2/sharing/create_shared_link_with_settings';\n var options = {\n body: {path: getDropboxPath(path)}\n };\n\n return this._request('POST', url, options).then((response) => {\n if (response.status !== 200 && response.status !== 409) {\n return Promise.reject(new Error('Invalid response status:' + response.status));\n }\n\n var body;\n\n try {\n body = JSON.parse(response.responseText);\n } catch (e) {\n return Promise.reject(new Error('Invalid response body: ' + response.responseText));\n }\n\n if (response.status === 409) {\n if (compareApiError(body, ['shared_link_already_exists'])) {\n return this._getSharedLink(path);\n }\n\n return Promise.reject(new Error('API error: ' + body.error_summary));\n }\n\n return Promise.resolve(body.url);\n }).then((link) => {\n this._itemRefs[path] = link;\n\n if (hasLocalStorage) {\n localStorage.setItem(SETTINGS_KEY+':shares', JSON.stringify(this._itemRefs));\n }\n\n return Promise.resolve(link);\n }, (error) => {\n error.message = 'Sharing Dropbox file or folder (\"' + path + '\") failed: ' + error.message;\n return Promise.reject(error);\n });\n },\n\n /**\n * Fetches the user's info from dropbox and returns a promise for it.\n *\n * @return {Promise} a promise for user info object (email - the user's email address)\n *\n * @protected\n */\n info: function () {\n var url = 'https://api.dropboxapi.com/2/users/get_current_account';\n\n return this._request('POST', url, {}).then(function (response) {\n var info = response.responseText;\n\n try {\n info = JSON.parse(info);\n } catch (e) {\n return Promise.reject(new Error('Could not query current account info: Invalid API response: ' + info));\n }\n\n return Promise.resolve({\n email: info.email\n });\n });\n },\n\n /**\n * Make a network request.\n *\n * @param {string} method - Request method\n * @param {string} url - Target URL\n * @param {object} options - Request options\n * @returns {Promise} Resolves with the response of the network request\n *\n * @private\n */\n _request: function (method, url, options) {\n var self = this;\n\n if (!options.headers) {\n options.headers = {};\n }\n options.headers['Authorization'] = 'Bearer ' + this.token;\n\n if (typeof options.body === 'object' && !isBinaryData(options.body)) {\n options.body = JSON.stringify(options.body);\n options.headers['Content-Type'] = 'application/json; charset=UTF-8';\n }\n\n this.rs._emit('wire-busy', {\n method: method,\n isFolder: isFolder(url)\n });\n\n return WireClient.request.call(this, method, url, options).then(function(xhr) {\n // 503 means retry this later\n if (xhr && xhr.status === 503) {\n if (self.online) {\n self.online = false;\n self.rs._emit('network-offline');\n }\n return setTimeout(self._request(method, url, options), 3210);\n } else {\n if (!self.online) {\n self.online = true;\n self.rs._emit('network-online');\n }\n self.rs._emit('wire-done', {\n method: method,\n isFolder: isFolder(url),\n success: true\n });\n\n return Promise.resolve(xhr);\n }\n }, function(error) {\n if (self.online) {\n self.online = false;\n self.rs._emit('network-offline');\n }\n self.rs._emit('wire-done', {\n method: method,\n isFolder: isFolder(url),\n success: false\n });\n\n return Promise.reject(error);\n });\n },\n\n /**\n * Fetches the revision of all the files from dropbox API and puts them\n * into ``_revCache``. These values can then be used to determine if\n * something has changed.\n *\n * @private\n */\n fetchDelta: function () {\n // If fetchDelta was already called, and didn't finish, return the existing\n // promise instead of calling Dropbox API again\n if (this._fetchDeltaPromise) {\n return this._fetchDeltaPromise;\n }\n\n var args = Array.prototype.slice.call(arguments);\n var self = this;\n\n var fetch = function (cursor) {\n let url = 'https://api.dropboxapi.com/2/files/list_folder';\n let requestBody;\n\n if (typeof cursor === 'string') {\n url += '/continue';\n requestBody = { cursor };\n } else {\n requestBody = {\n path: PATH_PREFIX,\n recursive: true,\n include_deleted: true\n };\n }\n\n return self._request('POST', url, { body: requestBody }).then(function (response) {\n if (response.status === 401) {\n self.rs._emit('error', new Authorize.Unauthorized());\n return Promise.resolve(args);\n }\n\n if (response.status !== 200 && response.status !== 409) {\n return Promise.reject(new Error('Invalid response status: ' + response.status));\n }\n\n let responseBody;\n\n try {\n responseBody = JSON.parse(response.responseText);\n } catch (e) {\n return Promise.reject(new Error('Invalid response body: ' + response.responseText));\n }\n\n if (response.status === 409) {\n if (compareApiError(responseBody, ['path', 'not_found'])) {\n responseBody = {\n cursor: null,\n entries: [],\n has_more: false\n };\n } else {\n return Promise.reject(new Error('API returned an error: ' + responseBody.error_summary));\n }\n }\n\n if (!cursor) {\n //we are doing a complete fetch, so propagation would introduce unnecessary overhead\n self._revCache.deactivatePropagation();\n }\n\n responseBody.entries.forEach(function (entry) {\n var path = entry.path_lower.substr(PATH_PREFIX.length);\n\n if (entry['.tag'] === 'deleted') {\n // there's no way to know whether the entry was a file or a folder\n self._revCache.delete(path);\n self._revCache.delete(path + '/');\n } else if (entry['.tag'] === 'file') {\n self._revCache.set(path, entry.rev);\n }\n });\n\n self._fetchDeltaCursor = responseBody.cursor;\n if (responseBody.has_more) {\n return fetch(responseBody.cursor);\n } else {\n self._revCache.activatePropagation(); \n self._initialFetchDone = true;\n }\n }).catch((error) => {\n if (error === 'timeout' || error instanceof ProgressEvent) {\n // Offline is handled elsewhere already, just ignore it here\n return Promise.resolve();\n } else {\n return Promise.reject(error);\n }\n });\n };\n this._fetchDeltaPromise = fetch(self._fetchDeltaCursor).catch((error) => {\n if (typeof(error) === 'object' && 'message' in error) {\n error.message = 'Dropbox: fetchDelta: ' + error.message;\n } else {\n error = `Dropbox: fetchDelta: ${error}`;\n }\n this._fetchDeltaPromise = null;\n return Promise.reject(error);\n }).then(() => {\n this._fetchDeltaPromise = null;\n return Promise.resolve(args);\n });\n\n return this._fetchDeltaPromise;\n },\n\n /**\n * Gets metadata for a path (can point to either a file or a folder).\n *\n * @param {string} path - the path to get metadata for\n *\n * @returns {Promise} A promise for the metadata\n *\n * @private\n */\n _getMetadata: function (path) {\n const url = 'https://api.dropboxapi.com/2/files/get_metadata';\n const requestBody = {\n path: getDropboxPath(path)\n };\n\n return this._request('POST', url, { body: requestBody }).then((response) => {\n if (response.status !== 200 && response.status !== 409) {\n return Promise.reject(new Error('Invalid response status:' + response.status));\n }\n\n let responseBody;\n\n try {\n responseBody = JSON.parse(response.responseText);\n } catch (e) {\n return Promise.reject(new Error('Invalid response body: ' + response.responseText));\n }\n\n if (response.status === 409) {\n if (compareApiError(responseBody, ['path', 'not_found'])) {\n return Promise.resolve();\n }\n\n return Promise.reject(new Error('API error: ' + responseBody.error_summary));\n }\n\n return Promise.resolve(responseBody);\n }).then(undefined, (error) => {\n error.message = 'Could not load metadata for file or folder (\"' + path + '\"): ' + error.message;\n return Promise.reject(error);\n });\n },\n\n /**\n * Upload a simple file (the size is no more than 150MB).\n *\n * @param {Object} params\n * @param {string} options.ifMatch - Only update the file if its ETag\n * matches this string\n * @param {string} options.path - path of the file\n * @param {string} options.body - contents of the file to upload\n * @param {string} options.contentType - mime type of the file\n *\n * @return {Promise} A promise for an object with the following structure:\n * statusCode - HTTP status code\n * revision - revision of the newly-created file, if any\n *\n * @private\n */\n _uploadSimple: function (params) {\n var url = 'https://content.dropboxapi.com/2/files/upload';\n var args = {\n path: getDropboxPath(params.path),\n mode: {'.tag': 'overwrite'},\n mute: true\n };\n\n if (params.ifMatch) {\n args.mode = {'.tag': 'update', update: params.ifMatch};\n }\n\n return this._request('POST', url, {\n body: params.body,\n headers: {\n 'Content-Type': 'application/octet-stream',\n 'Dropbox-API-Arg': JSON.stringify(args)\n }\n }).then((response) => {\n if (response.status !== 200 && response.status !== 409) {\n return Promise.resolve({statusCode: response.status});\n }\n\n var body = response.responseText;\n\n try {\n body = JSON.parse(body);\n } catch (e) {\n return Promise.reject(new Error('Invalid API result: ' + body));\n }\n\n if (response.status === 409) {\n if (compareApiError(body, ['path', 'conflict'])) {\n return this._getMetadata(params.path).then(function (metadata) {\n return Promise.resolve({\n statusCode: 412,\n revision: metadata.rev\n });\n });\n }\n return Promise.reject(new Error('API error: ' + body.error_summary));\n }\n\n this._revCache.set(params.path, body.rev);\n\n return Promise.resolve({ statusCode: response.status, revision: body.rev });\n });\n },\n\n /**\n * Deletes a file or a folder.\n *\n * @param {string} path - the path to delete\n *\n * @returns {Promise} A promise for an object with the following structure:\n * statusCode - HTTP status code\n *\n * @private\n */\n _deleteSimple: function (path) {\n const url = 'https://api.dropboxapi.com/2/files/delete';\n const requestBody = { path: getDropboxPath(path) };\n\n return this._request('POST', url, { body: requestBody }).then((response) => {\n if (response.status !== 200 && response.status !== 409) {\n return Promise.resolve({statusCode: response.status});\n }\n\n var responseBody = response.responseText;\n\n try {\n responseBody = JSON.parse(responseBody);\n } catch (e) {\n return Promise.reject(new Error('Invalid response body: ' + responseBody));\n }\n\n if (response.status === 409) {\n if (compareApiError(responseBody, ['path_lookup', 'not_found'])) {\n return Promise.resolve({statusCode: 404});\n }\n return Promise.reject(new Error('API error: ' + responseBody.error_summary));\n }\n\n return Promise.resolve({statusCode: 200});\n }).then((result) => {\n if (result.statusCode === 200 || result.statusCode === 404) {\n this._revCache.delete(path);\n delete this._itemRefs[path];\n }\n return Promise.resolve(result);\n }, (error) => {\n error.message = 'Could not delete Dropbox file or folder (\"' + path + '\"): ' + error.message;\n return Promise.reject(error);\n });\n },\n\n /**\n * Requests the link for an already-shared file or folder.\n *\n * @param {string} path - path to the file or folder\n *\n * @returns {Promise} A promise for the shared link\n *\n * @private\n */\n _getSharedLink: function (path) {\n var url = 'https://api.dropbox.com/2/sharing/list_shared_links';\n var options = {\n body: {\n path: getDropboxPath(path),\n direct_only: true\n }\n };\n\n return this._request('POST', url, options).then((response) => {\n if (response.status !== 200 && response.status !== 409) {\n return Promise.reject(new Error('Invalid response status: ' + response.status));\n }\n\n var body;\n\n try {\n body = JSON.parse(response.responseText);\n } catch (e) {\n return Promise.reject(new Error('Invalid response body: ' + response.responseText));\n }\n\n if (response.status === 409) {\n return Promise.reject(new Error('API error: ' + response.error_summary));\n }\n\n if (!body.links.length) {\n return Promise.reject(new Error('No links returned'));\n }\n\n return Promise.resolve(body.links[0].url);\n }, (error) => {\n error.message = 'Could not get link to a shared file or folder (\"' + path + '\"): ' + error.message;\n return Promise.reject(error);\n });\n }\n};\n\n/**\n * Hooking the sync\n *\n * TODO: document\n */\nfunction hookSync(rs) {\n if (rs._dropboxOrigSync) { return; } // already hooked\n rs._dropboxOrigSync = rs.sync.sync.bind(rs.sync);\n rs.sync.sync = function () {\n return this.dropbox.fetchDelta.apply(this.dropbox, arguments).\n then(rs._dropboxOrigSync, function (err) {\n rs._emit('error', new Sync.SyncError(err));\n rs._emit('sync-done');\n });\n }.bind(rs);\n}\n\n/**\n * Unhooking the sync\n *\n * TODO: document\n */\nfunction unHookSync(rs) {\n if (! rs._dropboxOrigSync) { return; } // not hooked\n rs.sync.sync = rs._dropboxOrigSync;\n delete rs._dropboxOrigSync;\n}\n\n/**\n * Hook RemoteStorage.syncCycle as it's the first function called\n * after RemoteStorage.sync is initialized, so we can then hook\n * the sync function\n * @param {object} rs RemoteStorage instance\n */\nfunction hookSyncCycle(rs) {\n if (rs._dropboxOrigSyncCycle) { return; } // already hooked\n rs._dropboxOrigSyncCycle = rs.syncCycle;\n rs.syncCycle = () => {\n if (rs.sync) {\n hookSync(rs);\n rs._dropboxOrigSyncCycle(arguments);\n unHookSyncCycle(rs);\n } else {\n throw new Error('expected sync to be initialized by now');\n }\n };\n}\n\n/**\n * Restore RemoteStorage's syncCycle original implementation\n * @param {object} rs RemoteStorage instance\n */\nfunction unHookSyncCycle(rs) {\n if (!rs._dropboxOrigSyncCycle) { return; } // not hooked\n rs.syncCycle = rs._dropboxOrigSyncCycle;\n delete rs._dropboxOrigSyncCycle;\n}\n\n/**\n * Overwrite BaseClient's getItemURL with our own implementation\n *\n * TODO: getItemURL still needs to be implemented\n *\n * @param {object} rs - RemoteStorage instance\n *\n * @private\n */\nfunction hookGetItemURL (rs) {\n if (rs._origBaseClientGetItemURL) { return; }\n rs._origBaseClientGetItemURL = BaseClient.prototype.getItemURL;\n BaseClient.prototype.getItemURL = function (/*path*/) {\n throw new Error('getItemURL is not implemented for Dropbox yet');\n };\n}\n\n/**\n * Restore BaseClient's getItemURL original implementation\n *\n * @param {object} rs - RemoteStorage instance\n *\n * @private\n */\nfunction unHookGetItemURL(rs){\n if (! rs._origBaseClientGetItemURL) { return; }\n BaseClient.prototype.getItemURL = rs._origBaseClientGetItemURL;\n delete rs._origBaseClientGetItemURL;\n}\n\n/**\n * TODO: document\n */\nfunction hookRemote(rs){\n if (rs._origRemote) { return; }\n rs._origRemote = rs.remote;\n rs.remote = rs.dropbox;\n}\n\n/**\n * TODO: document\n */\nfunction unHookRemote(rs){\n if (rs._origRemote) {\n rs.remote = rs._origRemote;\n delete rs._origRemote;\n }\n}\n\n/**\n * TODO: document\n */\nfunction hookIt(rs){\n hookRemote(rs);\n if (rs.sync) {\n hookSync(rs);\n } else {\n // when sync is not available yet, we hook the syncCycle function which is called\n // right after sync is initialized\n hookSyncCycle(rs);\n }\n hookGetItemURL(rs);\n}\n\n/**\n * TODO: document\n */\nfunction unHookIt(rs){\n unHookRemote(rs);\n unHookSync(rs);\n unHookGetItemURL(rs);\n unHookSyncCycle(rs);\n}\n\n/**\n * Initialize the Dropbox backend.\n *\n * @param {object} remoteStorage - RemoteStorage instance\n *\n * @protected\n */\nDropbox._rs_init = function (rs) {\n hasLocalStorage = util.localStorageAvailable();\n if ( rs.apiKeys.dropbox ) {\n rs.dropbox = new Dropbox(rs);\n }\n if (rs.backend === 'dropbox') {\n hookIt(rs);\n }\n};\n\n/**\n * Inform about the availability of the Dropbox backend.\n *\n * @param {object} rs - RemoteStorage instance\n * @returns {Boolean}\n *\n * @protected\n */\nDropbox._rs_supported = function () {\n return true;\n};\n\n/**\n * Remove Dropbox as a backend.\n *\n * @param {object} remoteStorage - RemoteStorage instance\n *\n * @protected\n */\nDropbox._rs_cleanup = function (rs) {\n unHookIt(rs);\n if (hasLocalStorage){\n localStorage.removeItem(SETTINGS_KEY);\n }\n rs.setBackend(undefined);\n};\n\n\nmodule.exports = Dropbox;\n","const eventHandling = require('./eventhandling');\n\nconst mode = typeof(window) !== 'undefined' ? 'browser' : 'node',\n env = {};\n\nconst Env = function () {\n return env;\n};\n\nEnv.isBrowser = function () {\n return mode === \"browser\";\n};\n\nEnv.isNode = function () {\n return mode === \"node\";\n};\n\nEnv.goBackground = function () {\n Env._emit(\"background\");\n};\n\nEnv.goForeground = function () {\n Env._emit(\"foreground\");\n};\n\nEnv._rs_init = function (/* remoteStorage */) {\n eventHandling(Env, \"background\", \"foreground\");\n\n function visibility () {\n if (document[env.hiddenProperty]) {\n Env.goBackground();\n } else {\n Env.goForeground();\n }\n }\n\n if (mode === 'browser') {\n if (typeof(document.hidden) !== \"undefined\") {\n env.hiddenProperty = \"hidden\";\n env.visibilityChangeEvent = \"visibilitychange\";\n } else if (typeof(document.mozHidden) !== \"undefined\") {\n env.hiddenProperty = \"mozHidden\";\n env.visibilityChangeEvent = \"mozvisibilitychange\";\n } else if (typeof(document.msHidden) !== \"undefined\") {\n env.hiddenProperty = \"msHidden\";\n env.visibilityChangeEvent = \"msvisibilitychange\";\n } else if (typeof(document.webkitHidden) !== \"undefined\") {\n env.hiddenProperty = \"webkitHidden\";\n env.visibilityChangeEvent = \"webkitvisibilitychange\";\n }\n document.addEventListener(env.visibilityChangeEvent, visibility, false);\n visibility();\n }\n};\n\nEnv._rs_cleanup = function (/* remoteStorage */) {\n};\n\n\nmodule.exports = Env;\n","/**\n * @class GoogleDrive\n *\n * To use this backend, you need to specify the app's client ID like so:\n *\n * @example\n * remoteStorage.setApiKeys({\n * googledrive: 'your-client-id'\n * });\n *\n * A client ID can be obtained by registering your app in the Google\n * Developers Console: https://console.developers.google.com/flows/enableapi?apiid=drive\n *\n * Docs: https://developers.google.com/drive/v3/web/quickstart/js\n**/\n\nconst BaseClient = require('./baseclient');\nconst WireClient = require('./wireclient');\nconst eventHandling = require('./eventhandling');\nconst util = require('./util');\n\nconst BASE_URL = 'https://www.googleapis.com';\nconst AUTH_URL = 'https://accounts.google.com/o/oauth2/auth';\nconst AUTH_SCOPE = 'https://www.googleapis.com/auth/drive';\nconst SETTINGS_KEY = 'remotestorage:googledrive';\nconst PATH_PREFIX = '/remotestorage';\n\nconst GD_DIR_MIME_TYPE = 'application/vnd.google-apps.folder';\nconst RS_DIR_MIME_TYPE = 'application/json; charset=UTF-8';\n\nconst isFolder = util.isFolder;\nconst cleanPath = util.cleanPath;\nconst shouldBeTreatedAsBinary = util.shouldBeTreatedAsBinary;\nconst getJSONFromLocalStorage = util.getJSONFromLocalStorage;\nconst getTextFromArrayBuffer = util.getTextFromArrayBuffer;\n\nlet hasLocalStorage;\n\n/**\n * Produce a title from a filename for metadata.\n *\n * @param {string} filename\n * @returns {string} title\n *\n * @private\n */\nfunction metaTitleFromFileName (filename) {\n if (filename.substr(-1) === '/') {\n filename = filename.substr(0, filename.length - 1);\n }\n\n return decodeURIComponent(filename);\n}\n\n/**\n * Get the parent directory for the given path.\n *\n * @param {string} path\n * @returns {string} parent directory\n *\n * @private\n */\nfunction parentPath (path) {\n return path.replace(/[^\\/]+\\/?$/, '');\n}\n\n/**\n * Get only the filename from a full path.\n *\n * @param {string} path\n * @returns {string} filename\n *\n * @private\n */\nfunction baseName (path) {\n const parts = path.split('/');\n if (path.substr(-1) === '/') {\n return parts[parts.length-2]+'/';\n } else {\n return parts[parts.length-1];\n }\n}\n\n/**\n * Prepend the path with the remoteStorage base directory.\n *\n * @param {string} path - Path\n * @returns {string} Actual path on Google Drive\n *\n * @private\n */\nfunction googleDrivePath (path) {\n return cleanPath(`${PATH_PREFIX}/${path}`);\n}\n\n/**\n * Remove surrounding quotes from a string.\n *\n * @param {string} string - string with surrounding quotes\n * @returns {string} string without surrounding quotes\n *\n * @private\n */\nfunction removeQuotes (string) {\n return string.replace(/^[\"'](.*)[\"']$/, \"$1\");\n}\n\n/**\n * Internal cache object for storing Google file IDs.\n *\n * @param {number} maxAge - Maximum age (in seconds) the content should be cached for\n */\nconst Cache = function (maxAge) {\n this.maxAge = maxAge;\n this._items = {};\n};\n\nCache.prototype = {\n get: function (key) {\n const item = this._items[key];\n const now = new Date().getTime();\n return (item && item.t >= (now - this.maxAge)) ? item.v : undefined;\n },\n\n set: function (key, value) {\n this._items[key] = {\n v: value,\n t: new Date().getTime()\n };\n }\n};\n\nconst GoogleDrive = function (remoteStorage, clientId) {\n\n eventHandling(this, 'connected', 'not-connected');\n\n this.rs = remoteStorage;\n this.clientId = clientId;\n\n this._fileIdCache = new Cache(60 * 5); // IDs expire after 5 minutes (is this a good idea?)\n\n hasLocalStorage = util.localStorageAvailable();\n\n if (hasLocalStorage){\n const settings = getJSONFromLocalStorage(SETTINGS_KEY);\n if (settings) {\n this.configure(settings);\n }\n }\n\n};\n\nGoogleDrive.prototype = {\n connected: false,\n online: true,\n\n /**\n * Configure the Google Drive backend.\n *\n * Fetches the user info from Google when no ``userAddress`` is given.\n *\n * @param {Object} settings\n * @param {string} [settings.userAddress] - The user's email address\n * @param {string} [settings.token] - Authorization token\n *\n * @protected\n */\n configure: function (settings) { // Settings parameter compatible with WireClient\n // We only update this.userAddress if settings.userAddress is set to a string or to null\n if (typeof settings.userAddress !== 'undefined') { this.userAddress = settings.userAddress; }\n // Same for this.token. If only one of these two is set, we leave the other one at its existing value\n if (typeof settings.token !== 'undefined') { this.token = settings.token; }\n\n const writeSettingsToCache = function() {\n if (hasLocalStorage) {\n localStorage.setItem(SETTINGS_KEY, JSON.stringify({\n userAddress: this.userAddress,\n token: this.token\n }));\n }\n };\n\n const handleError = function() {\n this.connected = false;\n delete this.token;\n if (hasLocalStorage) {\n localStorage.removeItem(SETTINGS_KEY);\n }\n };\n\n if (this.token) {\n this.connected = true;\n\n if (this.userAddress) {\n this._emit('connected');\n writeSettingsToCache.apply(this);\n } else {\n this.info().then((info) => {\n this.userAddress = info.user.emailAddress;\n this._emit('connected');\n writeSettingsToCache.apply(this);\n }).catch(() => {\n handleError.apply(this);\n this.rs._emit('error', new Error('Could not fetch user info.'));\n });\n }\n } else {\n handleError.apply(this);\n }\n },\n\n /**\n * Initiate the authorization flow's OAuth dance.\n */\n connect: function () {\n this.rs.setBackend('googledrive');\n this.rs.authorize({ authURL: AUTH_URL, scope: AUTH_SCOPE, clientId: this.clientId });\n },\n\n /**\n * Stop the authorization process.\n *\n * @protected\n */\n stopWaitingForToken: function () {\n if (!this.connected) {\n this._emit('not-connected');\n }\n },\n\n /**\n * Request a resource (file or directory).\n *\n * @param {string} path - Path of the resource\n * @param {Object} options - Request options\n * @returns {Promise} Resolves with an object containing the status code,\n * body, content-type and revision\n *\n * @protected\n */\n get: function (path, options) {\n if (path.substr(-1) === '/') {\n return this._getFolder(googleDrivePath(path), options);\n } else {\n return this._getFile(googleDrivePath(path), options);\n }\n },\n\n /**\n * Create or update a file.\n *\n * @param {string} path - File path\n * @param body - File content\n * @param {string} contentType - File content-type\n * @param {Object} options\n * @param {string} options.ifNoneMatch - Only create of update the file if the\n * current ETag doesn't match this string\n * @returns {Promise} Resolves with an object containing the status code,\n * content-type and revision\n *\n * @protected\n */\n put: function (path, body, contentType, options) {\n const fullPath = googleDrivePath(path);\n\n function putDone(response) {\n if (response.status >= 200 && response.status < 300) {\n const meta = JSON.parse(response.responseText);\n const etagWithoutQuotes = removeQuotes(meta.etag);\n return Promise.resolve({statusCode: 200, contentType: meta.mimeType, revision: etagWithoutQuotes});\n } else if (response.status === 412) {\n return Promise.resolve({statusCode: 412, revision: 'conflict'});\n } else {\n return Promise.reject(\"PUT failed with status \" + response.status + \" (\" + response.responseText + \")\");\n }\n }\n\n return this._getFileId(fullPath).then((id) => {\n if (id) {\n if (options && (options.ifNoneMatch === '*')) {\n return putDone({ status: 412 });\n }\n return this._updateFile(id, fullPath, body, contentType, options).then(putDone);\n } else {\n return this._createFile(fullPath, body, contentType, options).then(putDone);\n }\n });\n },\n\n /**\n * Delete a file.\n *\n * @param {string} path - File path\n * @param {Object} options\n * @param {string} options.ifMatch - only delete the file if it's ETag\n * matches this string\n * @returns {Promise} Resolves with an object containing the status code\n *\n * @protected\n */\n 'delete': function (path, options) {\n const fullPath = googleDrivePath(path);\n\n return this._getFileId(fullPath).then((id) => {\n if (!id) {\n // File doesn't exist. Ignore.\n return Promise.resolve({statusCode: 200});\n }\n\n return this._getMeta(id).then((meta) => {\n let etagWithoutQuotes;\n if ((typeof meta === 'object') && (typeof meta.etag === 'string')) {\n etagWithoutQuotes = removeQuotes(meta.etag);\n }\n if (options && options.ifMatch && (options.ifMatch !== etagWithoutQuotes)) {\n return {statusCode: 412, revision: etagWithoutQuotes};\n }\n\n return this._request('DELETE', BASE_URL + '/drive/v2/files/' + id, {}).then((response) => {\n if (response.status === 200 || response.status === 204) {\n return {statusCode: 200};\n } else {\n return Promise.reject(\"Delete failed: \" + response.status + \" (\" + response.responseText + \")\");\n }\n });\n });\n });\n },\n\n /**\n * Fetch the user's info from Google.\n *\n * @returns {Promise} resolves with the user's info.\n *\n * @protected\n */\n info: function () {\n const url = BASE_URL + '/drive/v2/about?fields=user';\n // requesting user info(mainly for userAdress)\n return this._request('GET', url, {}).then(function (resp){\n try {\n const info = JSON.parse(resp.responseText);\n return Promise.resolve(info);\n } catch (e) {\n return Promise.reject(e);\n }\n });\n },\n\n /**\n * Update an existing file.\n *\n * @param {string} id - File ID\n * @param {string} path - File path\n * @param body - File content\n * @param {string} contentType - File content-type\n * @param {Object} options\n * @param {string} options.ifMatch - Only update the file if its ETag\n * matches this string\n * @returns {Promise} Resolves with the response of the network request\n *\n * @private\n */\n _updateFile: function (id, path, body, contentType, options) {\n const metadata = {\n mimeType: contentType\n };\n const headers = {\n 'Content-Type': 'application/json; charset=UTF-8'\n };\n\n if (options && options.ifMatch) {\n headers['If-Match'] = '\"' + options.ifMatch + '\"';\n }\n\n return this._request('PUT', BASE_URL + '/upload/drive/v2/files/' + id + '?uploadType=resumable', {\n body: JSON.stringify(metadata),\n headers: headers\n }).then((response) => {\n if (response.status === 412) {\n return (response);\n } else {\n return this._request('PUT', response.getResponseHeader('Location'), {\n body: contentType.match(/^application\\/json/) ? JSON.stringify(body) : body\n });\n }\n });\n },\n\n /**\n * Create a new file.\n *\n * @param {string} path - File path\n * @param body - File content\n * @param {string} contentType - File content-type\n * @returns {Promise} Resolves with the response of the network request\n *\n * @private\n */\n _createFile: function (path, body, contentType/*, options*/) {\n return this._getParentId(path).then((parentId) => {\n const fileName = baseName(path);\n const metadata = {\n title: metaTitleFromFileName(fileName),\n mimeType: contentType,\n parents: [{\n kind: \"drive#fileLink\",\n id: parentId\n }]\n };\n return this._request('POST', BASE_URL + '/upload/drive/v2/files?uploadType=resumable', {\n body: JSON.stringify(metadata),\n headers: {\n 'Content-Type': 'application/json; charset=UTF-8'\n }\n }).then((response) => {\n return this._request('POST', response.getResponseHeader('Location'), {\n body: contentType.match(/^application\\/json/) ? JSON.stringify(body) : body\n });\n });\n });\n },\n\n /**\n * Request a file.\n *\n * @param {string} path - File path\n * @param {Object} options\n * @param {string} [options.ifNoneMath] - Only return the file if its ETag\n * doesn't match the given string\n * @returns {Promise} Resolves with an object containing the status code,\n * body, content-type and revision\n *\n * @private\n */\n _getFile: function (path, options) {\n return this._getFileId(path).then((id) => {\n return this._getMeta(id).then((meta) => {\n let etagWithoutQuotes;\n if (typeof(meta) === 'object' && typeof(meta.etag) === 'string') {\n etagWithoutQuotes = removeQuotes(meta.etag);\n }\n\n if (options && options.ifNoneMatch && (etagWithoutQuotes === options.ifNoneMatch)) {\n return Promise.resolve({statusCode: 304});\n }\n\n if (!meta.downloadUrl) {\n if (meta.exportLinks && meta.exportLinks['text/html']) {\n // Documents that were generated inside GoogleDocs have no\n // downloadUrl, but you can export them to text/html instead:\n meta.mimeType += ';export=text/html';\n meta.downloadUrl = meta.exportLinks['text/html'];\n } else {\n // empty file\n return Promise.resolve({statusCode: 200, body: '', contentType: meta.mimeType, revision: etagWithoutQuotes});\n }\n }\n\n var params = {\n responseType: 'arraybuffer'\n }; \n return this._request('GET', meta.downloadUrl, params).then((response) => {\n //first encode the response as text, and later check if \n //text appears to actually be binary data\n return getTextFromArrayBuffer(response.response, 'UTF-8').then(function (responseText) { \n let body = responseText;\n if (meta.mimeType.match(/^application\\/json/)) {\n try {\n body = JSON.parse(body);\n } catch(e) {\n // body couldn't be parsed as JSON, so we'll just return it as is\n }\n } else {\n if (shouldBeTreatedAsBinary(responseText, meta.mimeType)) {\n //return unprocessed response \n body = response.response;\n }\n }\n\n return {statusCode: 200, body: body, contentType: meta.mimeType, revision: etagWithoutQuotes};\n });\n });\n });\n });\n },\n\n /**\n * Request a directory.\n *\n * @param {string} path - Directory path\n * @param {Object} options\n * @returns {Promise} Resolves with an object containing the status code,\n * body and content-type\n *\n * @private\n */\n _getFolder: function (path/*, options*/) {\n return this._getFileId(path).then((id) => {\n let query, fields, data, etagWithoutQuotes, itemsMap;\n if (! id) {\n return Promise.resolve({statusCode: 404});\n }\n\n query = '\\'' + id + '\\' in parents';\n fields = 'items(downloadUrl,etag,fileSize,id,mimeType,title)';\n return this._request('GET', BASE_URL + '/drive/v2/files?'\n + 'q=' + encodeURIComponent(query)\n + '&fields=' + encodeURIComponent(fields)\n + '&maxResults=1000',\n {})\n .then((response) => {\n if (response.status !== 200) {\n return Promise.reject('request failed or something: ' + response.status);\n }\n\n try {\n data = JSON.parse(response.responseText);\n } catch(e) {\n return Promise.reject('non-JSON response from GoogleDrive');\n }\n\n itemsMap = {};\n for (const item of data.items) {\n etagWithoutQuotes = removeQuotes(item.etag);\n if (item.mimeType === GD_DIR_MIME_TYPE) {\n this._fileIdCache.set(path + item.title + '/', item.id);\n itemsMap[item.title + '/'] = {\n ETag: etagWithoutQuotes\n };\n } else {\n this._fileIdCache.set(path + item.title, item.id);\n itemsMap[item.title] = {\n ETag: etagWithoutQuotes,\n 'Content-Type': item.mimeType,\n 'Content-Length': item.fileSize\n };\n }\n }\n\n // FIXME: add revision of folder!\n return Promise.resolve({statusCode: 200, body: itemsMap, contentType: RS_DIR_MIME_TYPE, revision: undefined});\n });\n });\n },\n\n /**\n * Get the ID of a parent path.\n *\n * Creates the directory if it doesn't exist yet.\n *\n * @param {string} path - Full path of a directory or file\n * @returns {Promise} Resolves with ID of the parent directory.\n *\n * @private\n */\n _getParentId: function (path) {\n const foldername = parentPath(path);\n\n return this._getFileId(foldername).then((parentId) => {\n if (parentId) {\n return Promise.resolve(parentId);\n } else {\n return this._createFolder(foldername);\n }\n });\n },\n\n /**\n * Create a directory.\n *\n * Creates all parent directories as well if any of them didn't exist yet.\n *\n * @param {string} path - Directory path\n * @returns {Promise} Resolves with the ID of the new directory\n *\n * @private\n */\n _createFolder: function (path) {\n return this._getParentId(path).then((parentId) => {\n return this._request('POST', BASE_URL + '/drive/v2/files', {\n body: JSON.stringify({\n title: metaTitleFromFileName(baseName(path)),\n mimeType: GD_DIR_MIME_TYPE,\n parents: [{\n id: parentId\n }]\n }),\n headers: {\n 'Content-Type': 'application/json; charset=UTF-8'\n }\n }).then((response) => {\n const meta = JSON.parse(response.responseText);\n return Promise.resolve(meta.id);\n });\n });\n },\n\n /**\n * Get the ID of a file.\n *\n * @param {string} path - File path\n * @returns {Promise} Resolves with the ID\n *\n * @private\n */\n _getFileId: function (path) {\n let id;\n\n if (path === '/') {\n // \"root\" is a special alias for the fileId of the root folder\n return Promise.resolve('root');\n } else if ((id = this._fileIdCache.get(path))) {\n // id is cached.\n return Promise.resolve(id);\n }\n // id is not cached (or file doesn't exist).\n // load parent folder listing to propagate / update id cache.\n return this._getFolder(parentPath(path)).then(() => {\n id = this._fileIdCache.get(path);\n if (!id) {\n if (path.substr(-1) === '/') {\n return this._createFolder(path).then(() => {\n return this._getFileId(path);\n });\n } else {\n return Promise.resolve();\n }\n }\n return Promise.resolve(id);\n });\n },\n\n /**\n * Get the metadata for a given file ID.\n *\n * @param {string} id - File ID\n * @returns {Promise} Resolves with an object containing the metadata\n *\n * @private\n */\n _getMeta: function (id) {\n return this._request('GET', BASE_URL + '/drive/v2/files/' + id, {}).then(function (response) {\n if (response.status === 200) {\n return Promise.resolve(JSON.parse(response.responseText));\n } else {\n return Promise.reject(\"request (getting metadata for \" + id + \") failed with status: \" + response.status);\n }\n });\n },\n\n /**\n * Make a network request.\n *\n * @param {string} method - Request method\n * @param {string} url - Target URL\n * @param {Object} options - Request options\n * @returns {Promise} Resolves with the response of the network request\n *\n * @private\n */\n _request: function (method, url, options) {\n if (! options.headers) { options.headers = {}; }\n options.headers['Authorization'] = 'Bearer ' + this.token;\n\n this.rs._emit('wire-busy', {\n method: method,\n isFolder: isFolder(url)\n });\n\n return WireClient.request.call(this, method, url, options).then((xhr) => {\n // Google tokens expire from time to time...\n if (xhr && xhr.status === 401) {\n this.connect();\n return;\n } else {\n if (!this.online) {\n this.online = true;\n this.rs._emit('network-online');\n }\n this.rs._emit('wire-done', {\n method: method,\n isFolder: isFolder(url),\n success: true\n });\n\n return Promise.resolve(xhr);\n }\n }, (error) => {\n if (this.online) {\n this.online = false;\n this.rs._emit('network-offline');\n }\n this.rs._emit('wire-done', {\n method: method,\n isFolder: isFolder(url),\n success: false\n });\n\n return Promise.reject(error);\n });\n }\n};\n\n/**\n * Overwrite BaseClient's getItemURL with our own implementation\n *\n * TODO: Still needs to be implemented. At the moment it just throws\n * and error saying that it's not implemented yet.\n *\n * @param {object} rs - RemoteStorage instance\n *\n * @private\n */\nfunction hookGetItemURL (rs) {\n if (rs._origBaseClientGetItemURL) { return; }\n rs._origBaseClientGetItemURL = BaseClient.prototype.getItemURL;\n BaseClient.prototype.getItemURL = function (/* path */){\n throw new Error('getItemURL is not implemented for Google Drive yet');\n };\n}\n\n/**\n * Restore BaseClient's getItemURL original implementation\n *\n * @param {object} rs - RemoteStorage instance\n *\n * @private\n */\nfunction unHookGetItemURL (rs) {\n if (!rs._origBaseClientGetItemURL) { return; }\n BaseClient.prototype.getItemURL = rs._origBaseClientGetItemURL;\n delete rs._origBaseClientGetItemURL;\n}\n\n/**\n * Initialize the Google Drive backend.\n *\n * @param {Object} remoteStorage - RemoteStorage instance\n *\n * @protected\n */\nGoogleDrive._rs_init = function (remoteStorage) {\n const config = remoteStorage.apiKeys.googledrive;\n if (config) {\n remoteStorage.googledrive = new GoogleDrive(remoteStorage, config.clientId);\n if (remoteStorage.backend === 'googledrive') {\n remoteStorage._origRemote = remoteStorage.remote;\n remoteStorage.remote = remoteStorage.googledrive;\n\n hookGetItemURL(remoteStorage);\n }\n }\n};\n\n/**\n * Inform about the availability of the Google Drive backend.\n *\n * @param {Object} rs - RemoteStorage instance\n * @returns {Boolean}\n *\n * @protected\n */\nGoogleDrive._rs_supported = function () {\n return true;\n};\n\n/**\n * Remove Google Drive as a backend.\n *\n * @param {Object} remoteStorage - RemoteStorage instance\n *\n * @protected\n */\nGoogleDrive._rs_cleanup = function (remoteStorage) {\n remoteStorage.setBackend(undefined);\n if (remoteStorage._origRemote) {\n remoteStorage.remote = remoteStorage._origRemote;\n delete remoteStorage._origRemote;\n }\n unHookGetItemURL(remoteStorage);\n};\n\nmodule.exports = GoogleDrive;\n","'use strict';\n\nconst log = require('./log');\nconst util = require('./util');\nconst WebFinger = require('webfinger.js');\n\n// feature detection flags\nvar haveXMLHttpRequest, hasLocalStorage;\n\n// used to store settings in localStorage\nvar SETTINGS_KEY = 'remotestorage:discover';\n\n// cache loaded from localStorage\nvar cachedInfo = {};\n\n/**\n * This function deals with the Webfinger lookup, discovering a connecting\n * user's storage details.\n *\n * @param {string} userAddress - user@host\n *\n * @returns {Promise} A promise for an object with the following properties.\n * href - Storage base URL,\n * storageApi - RS protocol version,\n * authUrl - OAuth URL,\n * properties - Webfinger link properties\n **/\n\nconst Discover = function Discover(userAddress) {\n return new Promise((resolve, reject) => {\n\n if (userAddress in cachedInfo) {\n return resolve(cachedInfo[userAddress]);\n }\n\n var webFinger = new WebFinger({\n tls_only: false,\n uri_fallback: true,\n request_timeout: 5000\n });\n\n return webFinger.lookup(userAddress, function (err, response) {\n if (err) {\n return reject(err);\n } else if ((typeof response.idx.links.remotestorage !== 'object') ||\n (typeof response.idx.links.remotestorage.length !== 'number') ||\n (response.idx.links.remotestorage.length <= 0)) {\n log(\"[Discover] WebFinger record for \" + userAddress + \" does not have remotestorage defined in the links section \", JSON.stringify(response.json));\n return reject(\"WebFinger record for \" + userAddress + \" does not have remotestorage defined in the links section.\");\n }\n\n var rs = response.idx.links.remotestorage[0];\n var authURL = rs.properties['http://tools.ietf.org/html/rfc6749#section-4.2'] ||\n rs.properties['auth-endpoint'];\n var storageApi = rs.properties['http://remotestorage.io/spec/version'] ||\n rs.type;\n\n // cache fetched data\n cachedInfo[userAddress] = {\n href: rs.href,\n storageApi: storageApi,\n authURL: authURL,\n properties: rs.properties\n };\n\n if (hasLocalStorage) {\n localStorage[SETTINGS_KEY] = JSON.stringify({ cache: cachedInfo });\n }\n\n return resolve(cachedInfo[userAddress]);\n });\n });\n};\n\nDiscover.DiscoveryError = function(message) {\n this.name = 'DiscoveryError';\n this.message = message;\n this.stack = (new Error()).stack;\n};\nDiscover.DiscoveryError.prototype = Object.create(Error.prototype);\nDiscover.DiscoveryError.prototype.constructor = Discover.DiscoveryError;\n\nDiscover._rs_init = function (/*remoteStorage*/) {\n hasLocalStorage = util.localStorageAvailable();\n if (hasLocalStorage) {\n var settings;\n try { settings = JSON.parse(localStorage[SETTINGS_KEY]); } catch(e) { /* empty */ }\n if (settings) {\n cachedInfo = settings.cache;\n }\n }\n};\n\nDiscover._rs_supported = function () {\n haveXMLHttpRequest = !! util.globalContext.XMLHttpRequest;\n return haveXMLHttpRequest;\n};\n\nDiscover._rs_cleanup = function () {\n if (hasLocalStorage) {\n delete localStorage[SETTINGS_KEY];\n }\n};\n\n\nmodule.exports = Discover;\n","/**\n * @class Access\n *\n * Keeps track of claimed access and scopes.\n */\nvar Access = function() {\n this.reset();\n};\n\nAccess.prototype = {\n\n /**\n * Claim access on a given scope with given mode.\n *\n * @param {string} scope - An access scope, such as \"contacts\" or \"calendar\"\n * @param {string} mode - Access mode. Either \"r\" for read-only or \"rw\" for read/write\n */\n claim: function(scope, mode) {\n if (typeof(scope) !== 'string' || scope.indexOf('/') !== -1 || scope.length === 0) {\n throw new Error('Scope should be a non-empty string without forward slashes');\n }\n if (!mode.match(/^rw?$/)) {\n throw new Error('Mode should be either \\'r\\' or \\'rw\\'');\n }\n this._adjustRootPaths(scope);\n this.scopeModeMap[scope] = mode;\n },\n\n /**\n * Get the access mode for a given scope.\n *\n * @param {string} scope - Access scope\n * @returns {string} Access mode\n */\n get: function(scope) {\n return this.scopeModeMap[scope];\n },\n\n /**\n * Remove access for the given scope.\n *\n * @param {string} scope - Access scope\n */\n remove: function(scope) {\n var savedMap = {};\n var name;\n for (name in this.scopeModeMap) {\n savedMap[name] = this.scopeModeMap[name];\n }\n this.reset();\n delete savedMap[scope];\n for (name in savedMap) {\n this.set(name, savedMap[name]);\n }\n },\n\n /**\n * Verify permission for a given scope.\n *\n * @param {string} scope - Access scope\n * @param {string} mode - Access mode\n * @returns {boolean} true if the requested access mode is active, false otherwise\n */\n checkPermission: function(scope, mode) {\n var actualMode = this.get(scope);\n return actualMode && (mode === 'r' || actualMode === 'rw');\n },\n\n /**\n * Verify permission for a given path.\n *\n * @param {string} path - Path\n * @param {string} mode - Access mode\n * @returns {boolean} true if the requested access mode is active, false otherwise\n */\n checkPathPermission: function(path, mode) {\n if (this.checkPermission('*', mode)) {\n return true;\n }\n return !!this.checkPermission(this._getModuleName(path), mode);\n },\n\n\n /**\n * Reset all access permissions.\n */\n reset: function() {\n this.rootPaths = [];\n this.scopeModeMap = {};\n },\n\n /**\n * Return the module name for a given path.\n *\n * @private\n */\n _getModuleName: function(path) {\n if (path[0] !== '/') {\n throw new Error('Path should start with a slash');\n }\n var moduleMatch = path.replace(/^\\/public/, '').match(/^\\/([^/]*)\\//);\n return moduleMatch ? moduleMatch[1] : '*';\n },\n\n\n /**\n * TODO: document\n *\n * @param {string} newScope\n *\n * @private\n */\n _adjustRootPaths: function(newScope) {\n if ('*' in this.scopeModeMap || newScope === '*') {\n this.rootPaths = ['/'];\n } else if (! (newScope in this.scopeModeMap)) {\n this.rootPaths.push('/' + newScope + '/');\n this.rootPaths.push('/public/' + newScope + '/');\n }\n },\n\n /**\n * TODO: document\n *\n * @param {string} scope\n * @returns {string}\n *\n * @private\n */\n _scopeNameForParameter: function(scope) {\n if (scope.name === '*' && this.storageType) {\n if (this.storageType === '2012.04') {\n return '';\n } else if (this.storageType.match(/remotestorage-0[01]/)) {\n return 'root';\n }\n }\n return scope.name;\n },\n\n /**\n * Set the storage type of the remote.\n *\n * @param {string} type - Storage type\n */\n setStorageType: function(type) {\n this.storageType = type;\n }\n};\n\n/**\n * Property: scopes\n *\n * Holds an array of claimed scopes in the form\n * > { name: \"\", mode: \"\" }\n */\nObject.defineProperty(Access.prototype, 'scopes', {\n get: function() {\n return Object.keys(this.scopeModeMap).map(function(key) {\n return { name: key, mode: this.scopeModeMap[key] };\n }.bind(this));\n }\n});\n\nObject.defineProperty(Access.prototype, 'scopeParameter', {\n get: function() {\n return this.scopes.map(function(scope) {\n return this._scopeNameForParameter(scope) + ':' + scope.mode;\n }.bind(this)).join(' ');\n }\n});\n\n\nAccess._rs_init = function() {};\n\nmodule.exports = Access;\n","/**\n * @class Caching\n *\n * Holds/manages caching configuration.\n **/\n\nvar util = require('./util');\nvar log = require('./log');\n\nvar containingFolder = util.containingFolder;\n\nvar Caching = function () {\n this.reset();\n};\n\nCaching.prototype = {\n pendingActivations: [],\n\n /**\n * Configure caching for a given path explicitly.\n *\n * Not needed when using ``enable``/``disable``.\n *\n * @param {string} path - Path to cache\n * @param {string} strategy - Caching strategy. One of 'ALL', 'SEEN', or 'FLUSH'.\n *\n */\n set: function (path, strategy) {\n if (typeof path !== 'string') {\n throw new Error('path should be a string');\n }\n if (!util.isFolder(path)) {\n throw new Error('path should be a folder');\n }\n if (this._remoteStorage && this._remoteStorage.access &&\n !this._remoteStorage.access.checkPathPermission(path, 'r')) {\n throw new Error('No access to path \"'+path+'\". You have to claim access to it first.');\n }\n if (!strategy.match(/^(FLUSH|SEEN|ALL)$/)) {\n throw new Error(\"strategy should be 'FLUSH', 'SEEN', or 'ALL'\");\n }\n\n this._rootPaths[path] = strategy;\n\n if (strategy === 'ALL') {\n if (this.activateHandler) {\n this.activateHandler(path);\n } else {\n this.pendingActivations.push(path);\n }\n }\n },\n\n /**\n * Enable caching for a given path.\n *\n * Uses caching strategy ``ALL``.\n *\n * @param {string} path - Path to enable caching for\n */\n enable: function (path) {\n this.set(path, 'ALL');\n },\n\n /**\n * Disable caching for a given path.\n *\n * Uses caching strategy ``FLUSH`` (meaning items are only cached until\n * successfully pushed to the remote).\n *\n * @param {string} path - Path to disable caching for\n */\n disable: function (path) {\n this.set(path, 'FLUSH');\n },\n\n /**\n * Set a callback for when caching is activated for a path.\n *\n * @param {function} callback - Callback function\n */\n onActivate: function (cb) {\n var i;\n log('[Caching] Setting activate handler', cb, this.pendingActivations);\n this.activateHandler = cb;\n for (i=0; i \n * @license MIT\n */\n/* eslint-disable no-proto */\n\n'use strict'\n\nvar base64 = require('base64-js')\nvar ieee754 = require('ieee754')\nvar isArray = require('isarray')\n\nexports.Buffer = Buffer\nexports.SlowBuffer = SlowBuffer\nexports.INSPECT_MAX_BYTES = 50\n\n/**\n * If `Buffer.TYPED_ARRAY_SUPPORT`:\n * === true Use Uint8Array implementation (fastest)\n * === false Use Object implementation (most compatible, even IE6)\n *\n * Browsers that support typed arrays are IE 10+, Firefox 4+, Chrome 7+, Safari 5.1+,\n * Opera 11.6+, iOS 4.2+.\n *\n * Due to various browser bugs, sometimes the Object implementation will be used even\n * when the browser supports typed arrays.\n *\n * Note:\n *\n * - Firefox 4-29 lacks support for adding new properties to `Uint8Array` instances,\n * See: https://bugzilla.mozilla.org/show_bug.cgi?id=695438.\n *\n * - Chrome 9-10 is missing the `TypedArray.prototype.subarray` function.\n *\n * - IE10 has a broken `TypedArray.prototype.subarray` function which returns arrays of\n * incorrect length in some situations.\n\n * We detect these buggy browsers and set `Buffer.TYPED_ARRAY_SUPPORT` to `false` so they\n * get the Object implementation, which is slower but behaves correctly.\n */\nBuffer.TYPED_ARRAY_SUPPORT = global.TYPED_ARRAY_SUPPORT !== undefined\n ? global.TYPED_ARRAY_SUPPORT\n : typedArraySupport()\n\n/*\n * Export kMaxLength after typed array support is determined.\n */\nexports.kMaxLength = kMaxLength()\n\nfunction typedArraySupport () {\n try {\n var arr = new Uint8Array(1)\n arr.__proto__ = {__proto__: Uint8Array.prototype, foo: function () { return 42 }}\n return arr.foo() === 42 && // typed array instances can be augmented\n typeof arr.subarray === 'function' && // chrome 9-10 lack `subarray`\n arr.subarray(1, 1).byteLength === 0 // ie10 has broken `subarray`\n } catch (e) {\n return false\n }\n}\n\nfunction kMaxLength () {\n return Buffer.TYPED_ARRAY_SUPPORT\n ? 0x7fffffff\n : 0x3fffffff\n}\n\nfunction createBuffer (that, length) {\n if (kMaxLength() < length) {\n throw new RangeError('Invalid typed array length')\n }\n if (Buffer.TYPED_ARRAY_SUPPORT) {\n // Return an augmented `Uint8Array` instance, for best performance\n that = new Uint8Array(length)\n that.__proto__ = Buffer.prototype\n } else {\n // Fallback: Return an object instance of the Buffer class\n if (that === null) {\n that = new Buffer(length)\n }\n that.length = length\n }\n\n return that\n}\n\n/**\n * The Buffer constructor returns instances of `Uint8Array` that have their\n * prototype changed to `Buffer.prototype`. Furthermore, `Buffer` is a subclass of\n * `Uint8Array`, so the returned instances will have all the node `Buffer` methods\n * and the `Uint8Array` methods. Square bracket notation works as expected -- it\n * returns a single octet.\n *\n * The `Uint8Array` prototype remains unmodified.\n */\n\nfunction Buffer (arg, encodingOrOffset, length) {\n if (!Buffer.TYPED_ARRAY_SUPPORT && !(this instanceof Buffer)) {\n return new Buffer(arg, encodingOrOffset, length)\n }\n\n // Common case.\n if (typeof arg === 'number') {\n if (typeof encodingOrOffset === 'string') {\n throw new Error(\n 'If encoding is specified then the first argument must be a string'\n )\n }\n return allocUnsafe(this, arg)\n }\n return from(this, arg, encodingOrOffset, length)\n}\n\nBuffer.poolSize = 8192 // not used by this implementation\n\n// TODO: Legacy, not needed anymore. Remove in next major version.\nBuffer._augment = function (arr) {\n arr.__proto__ = Buffer.prototype\n return arr\n}\n\nfunction from (that, value, encodingOrOffset, length) {\n if (typeof value === 'number') {\n throw new TypeError('\"value\" argument must not be a number')\n }\n\n if (typeof ArrayBuffer !== 'undefined' && value instanceof ArrayBuffer) {\n return fromArrayBuffer(that, value, encodingOrOffset, length)\n }\n\n if (typeof value === 'string') {\n return fromString(that, value, encodingOrOffset)\n }\n\n return fromObject(that, value)\n}\n\n/**\n * Functionally equivalent to Buffer(arg, encoding) but throws a TypeError\n * if value is a number.\n * Buffer.from(str[, encoding])\n * Buffer.from(array)\n * Buffer.from(buffer)\n * Buffer.from(arrayBuffer[, byteOffset[, length]])\n **/\nBuffer.from = function (value, encodingOrOffset, length) {\n return from(null, value, encodingOrOffset, length)\n}\n\nif (Buffer.TYPED_ARRAY_SUPPORT) {\n Buffer.prototype.__proto__ = Uint8Array.prototype\n Buffer.__proto__ = Uint8Array\n if (typeof Symbol !== 'undefined' && Symbol.species &&\n Buffer[Symbol.species] === Buffer) {\n // Fix subarray() in ES2016. See: https://github.com/feross/buffer/pull/97\n Object.defineProperty(Buffer, Symbol.species, {\n value: null,\n configurable: true\n })\n }\n}\n\nfunction assertSize (size) {\n if (typeof size !== 'number') {\n throw new TypeError('\"size\" argument must be a number')\n } else if (size < 0) {\n throw new RangeError('\"size\" argument must not be negative')\n }\n}\n\nfunction alloc (that, size, fill, encoding) {\n assertSize(size)\n if (size <= 0) {\n return createBuffer(that, size)\n }\n if (fill !== undefined) {\n // Only pay attention to encoding if it's a string. This\n // prevents accidentally sending in a number that would\n // be interpretted as a start offset.\n return typeof encoding === 'string'\n ? createBuffer(that, size).fill(fill, encoding)\n : createBuffer(that, size).fill(fill)\n }\n return createBuffer(that, size)\n}\n\n/**\n * Creates a new filled Buffer instance.\n * alloc(size[, fill[, encoding]])\n **/\nBuffer.alloc = function (size, fill, encoding) {\n return alloc(null, size, fill, encoding)\n}\n\nfunction allocUnsafe (that, size) {\n assertSize(size)\n that = createBuffer(that, size < 0 ? 0 : checked(size) | 0)\n if (!Buffer.TYPED_ARRAY_SUPPORT) {\n for (var i = 0; i < size; ++i) {\n that[i] = 0\n }\n }\n return that\n}\n\n/**\n * Equivalent to Buffer(num), by default creates a non-zero-filled Buffer instance.\n * */\nBuffer.allocUnsafe = function (size) {\n return allocUnsafe(null, size)\n}\n/**\n * Equivalent to SlowBuffer(num), by default creates a non-zero-filled Buffer instance.\n */\nBuffer.allocUnsafeSlow = function (size) {\n return allocUnsafe(null, size)\n}\n\nfunction fromString (that, string, encoding) {\n if (typeof encoding !== 'string' || encoding === '') {\n encoding = 'utf8'\n }\n\n if (!Buffer.isEncoding(encoding)) {\n throw new TypeError('\"encoding\" must be a valid string encoding')\n }\n\n var length = byteLength(string, encoding) | 0\n that = createBuffer(that, length)\n\n var actual = that.write(string, encoding)\n\n if (actual !== length) {\n // Writing a hex string, for example, that contains invalid characters will\n // cause everything after the first invalid character to be ignored. (e.g.\n // 'abxxcd' will be treated as 'ab')\n that = that.slice(0, actual)\n }\n\n return that\n}\n\nfunction fromArrayLike (that, array) {\n var length = array.length < 0 ? 0 : checked(array.length) | 0\n that = createBuffer(that, length)\n for (var i = 0; i < length; i += 1) {\n that[i] = array[i] & 255\n }\n return that\n}\n\nfunction fromArrayBuffer (that, array, byteOffset, length) {\n array.byteLength // this throws if `array` is not a valid ArrayBuffer\n\n if (byteOffset < 0 || array.byteLength < byteOffset) {\n throw new RangeError('\\'offset\\' is out of bounds')\n }\n\n if (array.byteLength < byteOffset + (length || 0)) {\n throw new RangeError('\\'length\\' is out of bounds')\n }\n\n if (byteOffset === undefined && length === undefined) {\n array = new Uint8Array(array)\n } else if (length === undefined) {\n array = new Uint8Array(array, byteOffset)\n } else {\n array = new Uint8Array(array, byteOffset, length)\n }\n\n if (Buffer.TYPED_ARRAY_SUPPORT) {\n // Return an augmented `Uint8Array` instance, for best performance\n that = array\n that.__proto__ = Buffer.prototype\n } else {\n // Fallback: Return an object instance of the Buffer class\n that = fromArrayLike(that, array)\n }\n return that\n}\n\nfunction fromObject (that, obj) {\n if (Buffer.isBuffer(obj)) {\n var len = checked(obj.length) | 0\n that = createBuffer(that, len)\n\n if (that.length === 0) {\n return that\n }\n\n obj.copy(that, 0, 0, len)\n return that\n }\n\n if (obj) {\n if ((typeof ArrayBuffer !== 'undefined' &&\n obj.buffer instanceof ArrayBuffer) || 'length' in obj) {\n if (typeof obj.length !== 'number' || isnan(obj.length)) {\n return createBuffer(that, 0)\n }\n return fromArrayLike(that, obj)\n }\n\n if (obj.type === 'Buffer' && isArray(obj.data)) {\n return fromArrayLike(that, obj.data)\n }\n }\n\n throw new TypeError('First argument must be a string, Buffer, ArrayBuffer, Array, or array-like object.')\n}\n\nfunction checked (length) {\n // Note: cannot use `length < kMaxLength()` here because that fails when\n // length is NaN (which is otherwise coerced to zero.)\n if (length >= kMaxLength()) {\n throw new RangeError('Attempt to allocate Buffer larger than maximum ' +\n 'size: 0x' + kMaxLength().toString(16) + ' bytes')\n }\n return length | 0\n}\n\nfunction SlowBuffer (length) {\n if (+length != length) { // eslint-disable-line eqeqeq\n length = 0\n }\n return Buffer.alloc(+length)\n}\n\nBuffer.isBuffer = function isBuffer (b) {\n return !!(b != null && b._isBuffer)\n}\n\nBuffer.compare = function compare (a, b) {\n if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) {\n throw new TypeError('Arguments must be Buffers')\n }\n\n if (a === b) return 0\n\n var x = a.length\n var y = b.length\n\n for (var i = 0, len = Math.min(x, y); i < len; ++i) {\n if (a[i] !== b[i]) {\n x = a[i]\n y = b[i]\n break\n }\n }\n\n if (x < y) return -1\n if (y < x) return 1\n return 0\n}\n\nBuffer.isEncoding = function isEncoding (encoding) {\n switch (String(encoding).toLowerCase()) {\n case 'hex':\n case 'utf8':\n case 'utf-8':\n case 'ascii':\n case 'latin1':\n case 'binary':\n case 'base64':\n case 'ucs2':\n case 'ucs-2':\n case 'utf16le':\n case 'utf-16le':\n return true\n default:\n return false\n }\n}\n\nBuffer.concat = function concat (list, length) {\n if (!isArray(list)) {\n throw new TypeError('\"list\" argument must be an Array of Buffers')\n }\n\n if (list.length === 0) {\n return Buffer.alloc(0)\n }\n\n var i\n if (length === undefined) {\n length = 0\n for (i = 0; i < list.length; ++i) {\n length += list[i].length\n }\n }\n\n var buffer = Buffer.allocUnsafe(length)\n var pos = 0\n for (i = 0; i < list.length; ++i) {\n var buf = list[i]\n if (!Buffer.isBuffer(buf)) {\n throw new TypeError('\"list\" argument must be an Array of Buffers')\n }\n buf.copy(buffer, pos)\n pos += buf.length\n }\n return buffer\n}\n\nfunction byteLength (string, encoding) {\n if (Buffer.isBuffer(string)) {\n return string.length\n }\n if (typeof ArrayBuffer !== 'undefined' && typeof ArrayBuffer.isView === 'function' &&\n (ArrayBuffer.isView(string) || string instanceof ArrayBuffer)) {\n return string.byteLength\n }\n if (typeof string !== 'string') {\n string = '' + string\n }\n\n var len = string.length\n if (len === 0) return 0\n\n // Use a for loop to avoid recursion\n var loweredCase = false\n for (;;) {\n switch (encoding) {\n case 'ascii':\n case 'latin1':\n case 'binary':\n return len\n case 'utf8':\n case 'utf-8':\n case undefined:\n return utf8ToBytes(string).length\n case 'ucs2':\n case 'ucs-2':\n case 'utf16le':\n case 'utf-16le':\n return len * 2\n case 'hex':\n return len >>> 1\n case 'base64':\n return base64ToBytes(string).length\n default:\n if (loweredCase) return utf8ToBytes(string).length // assume utf8\n encoding = ('' + encoding).toLowerCase()\n loweredCase = true\n }\n }\n}\nBuffer.byteLength = byteLength\n\nfunction slowToString (encoding, start, end) {\n var loweredCase = false\n\n // No need to verify that \"this.length <= MAX_UINT32\" since it's a read-only\n // property of a typed array.\n\n // This behaves neither like String nor Uint8Array in that we set start/end\n // to their upper/lower bounds if the value passed is out of range.\n // undefined is handled specially as per ECMA-262 6th Edition,\n // Section 13.3.3.7 Runtime Semantics: KeyedBindingInitialization.\n if (start === undefined || start < 0) {\n start = 0\n }\n // Return early if start > this.length. Done here to prevent potential uint32\n // coercion fail below.\n if (start > this.length) {\n return ''\n }\n\n if (end === undefined || end > this.length) {\n end = this.length\n }\n\n if (end <= 0) {\n return ''\n }\n\n // Force coersion to uint32. This will also coerce falsey/NaN values to 0.\n end >>>= 0\n start >>>= 0\n\n if (end <= start) {\n return ''\n }\n\n if (!encoding) encoding = 'utf8'\n\n while (true) {\n switch (encoding) {\n case 'hex':\n return hexSlice(this, start, end)\n\n case 'utf8':\n case 'utf-8':\n return utf8Slice(this, start, end)\n\n case 'ascii':\n return asciiSlice(this, start, end)\n\n case 'latin1':\n case 'binary':\n return latin1Slice(this, start, end)\n\n case 'base64':\n return base64Slice(this, start, end)\n\n case 'ucs2':\n case 'ucs-2':\n case 'utf16le':\n case 'utf-16le':\n return utf16leSlice(this, start, end)\n\n default:\n if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding)\n encoding = (encoding + '').toLowerCase()\n loweredCase = true\n }\n }\n}\n\n// The property is used by `Buffer.isBuffer` and `is-buffer` (in Safari 5-7) to detect\n// Buffer instances.\nBuffer.prototype._isBuffer = true\n\nfunction swap (b, n, m) {\n var i = b[n]\n b[n] = b[m]\n b[m] = i\n}\n\nBuffer.prototype.swap16 = function swap16 () {\n var len = this.length\n if (len % 2 !== 0) {\n throw new RangeError('Buffer size must be a multiple of 16-bits')\n }\n for (var i = 0; i < len; i += 2) {\n swap(this, i, i + 1)\n }\n return this\n}\n\nBuffer.prototype.swap32 = function swap32 () {\n var len = this.length\n if (len % 4 !== 0) {\n throw new RangeError('Buffer size must be a multiple of 32-bits')\n }\n for (var i = 0; i < len; i += 4) {\n swap(this, i, i + 3)\n swap(this, i + 1, i + 2)\n }\n return this\n}\n\nBuffer.prototype.swap64 = function swap64 () {\n var len = this.length\n if (len % 8 !== 0) {\n throw new RangeError('Buffer size must be a multiple of 64-bits')\n }\n for (var i = 0; i < len; i += 8) {\n swap(this, i, i + 7)\n swap(this, i + 1, i + 6)\n swap(this, i + 2, i + 5)\n swap(this, i + 3, i + 4)\n }\n return this\n}\n\nBuffer.prototype.toString = function toString () {\n var length = this.length | 0\n if (length === 0) return ''\n if (arguments.length === 0) return utf8Slice(this, 0, length)\n return slowToString.apply(this, arguments)\n}\n\nBuffer.prototype.equals = function equals (b) {\n if (!Buffer.isBuffer(b)) throw new TypeError('Argument must be a Buffer')\n if (this === b) return true\n return Buffer.compare(this, b) === 0\n}\n\nBuffer.prototype.inspect = function inspect () {\n var str = ''\n var max = exports.INSPECT_MAX_BYTES\n if (this.length > 0) {\n str = this.toString('hex', 0, max).match(/.{2}/g).join(' ')\n if (this.length > max) str += ' ... '\n }\n return ''\n}\n\nBuffer.prototype.compare = function compare (target, start, end, thisStart, thisEnd) {\n if (!Buffer.isBuffer(target)) {\n throw new TypeError('Argument must be a Buffer')\n }\n\n if (start === undefined) {\n start = 0\n }\n if (end === undefined) {\n end = target ? target.length : 0\n }\n if (thisStart === undefined) {\n thisStart = 0\n }\n if (thisEnd === undefined) {\n thisEnd = this.length\n }\n\n if (start < 0 || end > target.length || thisStart < 0 || thisEnd > this.length) {\n throw new RangeError('out of range index')\n }\n\n if (thisStart >= thisEnd && start >= end) {\n return 0\n }\n if (thisStart >= thisEnd) {\n return -1\n }\n if (start >= end) {\n return 1\n }\n\n start >>>= 0\n end >>>= 0\n thisStart >>>= 0\n thisEnd >>>= 0\n\n if (this === target) return 0\n\n var x = thisEnd - thisStart\n var y = end - start\n var len = Math.min(x, y)\n\n var thisCopy = this.slice(thisStart, thisEnd)\n var targetCopy = target.slice(start, end)\n\n for (var i = 0; i < len; ++i) {\n if (thisCopy[i] !== targetCopy[i]) {\n x = thisCopy[i]\n y = targetCopy[i]\n break\n }\n }\n\n if (x < y) return -1\n if (y < x) return 1\n return 0\n}\n\n// Finds either the first index of `val` in `buffer` at offset >= `byteOffset`,\n// OR the last index of `val` in `buffer` at offset <= `byteOffset`.\n//\n// Arguments:\n// - buffer - a Buffer to search\n// - val - a string, Buffer, or number\n// - byteOffset - an index into `buffer`; will be clamped to an int32\n// - encoding - an optional encoding, relevant is val is a string\n// - dir - true for indexOf, false for lastIndexOf\nfunction bidirectionalIndexOf (buffer, val, byteOffset, encoding, dir) {\n // Empty buffer means no match\n if (buffer.length === 0) return -1\n\n // Normalize byteOffset\n if (typeof byteOffset === 'string') {\n encoding = byteOffset\n byteOffset = 0\n } else if (byteOffset > 0x7fffffff) {\n byteOffset = 0x7fffffff\n } else if (byteOffset < -0x80000000) {\n byteOffset = -0x80000000\n }\n byteOffset = +byteOffset // Coerce to Number.\n if (isNaN(byteOffset)) {\n // byteOffset: it it's undefined, null, NaN, \"foo\", etc, search whole buffer\n byteOffset = dir ? 0 : (buffer.length - 1)\n }\n\n // Normalize byteOffset: negative offsets start from the end of the buffer\n if (byteOffset < 0) byteOffset = buffer.length + byteOffset\n if (byteOffset >= buffer.length) {\n if (dir) return -1\n else byteOffset = buffer.length - 1\n } else if (byteOffset < 0) {\n if (dir) byteOffset = 0\n else return -1\n }\n\n // Normalize val\n if (typeof val === 'string') {\n val = Buffer.from(val, encoding)\n }\n\n // Finally, search either indexOf (if dir is true) or lastIndexOf\n if (Buffer.isBuffer(val)) {\n // Special case: looking for empty string/buffer always fails\n if (val.length === 0) {\n return -1\n }\n return arrayIndexOf(buffer, val, byteOffset, encoding, dir)\n } else if (typeof val === 'number') {\n val = val & 0xFF // Search for a byte value [0-255]\n if (Buffer.TYPED_ARRAY_SUPPORT &&\n typeof Uint8Array.prototype.indexOf === 'function') {\n if (dir) {\n return Uint8Array.prototype.indexOf.call(buffer, val, byteOffset)\n } else {\n return Uint8Array.prototype.lastIndexOf.call(buffer, val, byteOffset)\n }\n }\n return arrayIndexOf(buffer, [ val ], byteOffset, encoding, dir)\n }\n\n throw new TypeError('val must be string, number or Buffer')\n}\n\nfunction arrayIndexOf (arr, val, byteOffset, encoding, dir) {\n var indexSize = 1\n var arrLength = arr.length\n var valLength = val.length\n\n if (encoding !== undefined) {\n encoding = String(encoding).toLowerCase()\n if (encoding === 'ucs2' || encoding === 'ucs-2' ||\n encoding === 'utf16le' || encoding === 'utf-16le') {\n if (arr.length < 2 || val.length < 2) {\n return -1\n }\n indexSize = 2\n arrLength /= 2\n valLength /= 2\n byteOffset /= 2\n }\n }\n\n function read (buf, i) {\n if (indexSize === 1) {\n return buf[i]\n } else {\n return buf.readUInt16BE(i * indexSize)\n }\n }\n\n var i\n if (dir) {\n var foundIndex = -1\n for (i = byteOffset; i < arrLength; i++) {\n if (read(arr, i) === read(val, foundIndex === -1 ? 0 : i - foundIndex)) {\n if (foundIndex === -1) foundIndex = i\n if (i - foundIndex + 1 === valLength) return foundIndex * indexSize\n } else {\n if (foundIndex !== -1) i -= i - foundIndex\n foundIndex = -1\n }\n }\n } else {\n if (byteOffset + valLength > arrLength) byteOffset = arrLength - valLength\n for (i = byteOffset; i >= 0; i--) {\n var found = true\n for (var j = 0; j < valLength; j++) {\n if (read(arr, i + j) !== read(val, j)) {\n found = false\n break\n }\n }\n if (found) return i\n }\n }\n\n return -1\n}\n\nBuffer.prototype.includes = function includes (val, byteOffset, encoding) {\n return this.indexOf(val, byteOffset, encoding) !== -1\n}\n\nBuffer.prototype.indexOf = function indexOf (val, byteOffset, encoding) {\n return bidirectionalIndexOf(this, val, byteOffset, encoding, true)\n}\n\nBuffer.prototype.lastIndexOf = function lastIndexOf (val, byteOffset, encoding) {\n return bidirectionalIndexOf(this, val, byteOffset, encoding, false)\n}\n\nfunction hexWrite (buf, string, offset, length) {\n offset = Number(offset) || 0\n var remaining = buf.length - offset\n if (!length) {\n length = remaining\n } else {\n length = Number(length)\n if (length > remaining) {\n length = remaining\n }\n }\n\n // must be an even number of digits\n var strLen = string.length\n if (strLen % 2 !== 0) throw new TypeError('Invalid hex string')\n\n if (length > strLen / 2) {\n length = strLen / 2\n }\n for (var i = 0; i < length; ++i) {\n var parsed = parseInt(string.substr(i * 2, 2), 16)\n if (isNaN(parsed)) return i\n buf[offset + i] = parsed\n }\n return i\n}\n\nfunction utf8Write (buf, string, offset, length) {\n return blitBuffer(utf8ToBytes(string, buf.length - offset), buf, offset, length)\n}\n\nfunction asciiWrite (buf, string, offset, length) {\n return blitBuffer(asciiToBytes(string), buf, offset, length)\n}\n\nfunction latin1Write (buf, string, offset, length) {\n return asciiWrite(buf, string, offset, length)\n}\n\nfunction base64Write (buf, string, offset, length) {\n return blitBuffer(base64ToBytes(string), buf, offset, length)\n}\n\nfunction ucs2Write (buf, string, offset, length) {\n return blitBuffer(utf16leToBytes(string, buf.length - offset), buf, offset, length)\n}\n\nBuffer.prototype.write = function write (string, offset, length, encoding) {\n // Buffer#write(string)\n if (offset === undefined) {\n encoding = 'utf8'\n length = this.length\n offset = 0\n // Buffer#write(string, encoding)\n } else if (length === undefined && typeof offset === 'string') {\n encoding = offset\n length = this.length\n offset = 0\n // Buffer#write(string, offset[, length][, encoding])\n } else if (isFinite(offset)) {\n offset = offset | 0\n if (isFinite(length)) {\n length = length | 0\n if (encoding === undefined) encoding = 'utf8'\n } else {\n encoding = length\n length = undefined\n }\n // legacy write(string, encoding, offset, length) - remove in v0.13\n } else {\n throw new Error(\n 'Buffer.write(string, encoding, offset[, length]) is no longer supported'\n )\n }\n\n var remaining = this.length - offset\n if (length === undefined || length > remaining) length = remaining\n\n if ((string.length > 0 && (length < 0 || offset < 0)) || offset > this.length) {\n throw new RangeError('Attempt to write outside buffer bounds')\n }\n\n if (!encoding) encoding = 'utf8'\n\n var loweredCase = false\n for (;;) {\n switch (encoding) {\n case 'hex':\n return hexWrite(this, string, offset, length)\n\n case 'utf8':\n case 'utf-8':\n return utf8Write(this, string, offset, length)\n\n case 'ascii':\n return asciiWrite(this, string, offset, length)\n\n case 'latin1':\n case 'binary':\n return latin1Write(this, string, offset, length)\n\n case 'base64':\n // Warning: maxLength not taken into account in base64Write\n return base64Write(this, string, offset, length)\n\n case 'ucs2':\n case 'ucs-2':\n case 'utf16le':\n case 'utf-16le':\n return ucs2Write(this, string, offset, length)\n\n default:\n if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding)\n encoding = ('' + encoding).toLowerCase()\n loweredCase = true\n }\n }\n}\n\nBuffer.prototype.toJSON = function toJSON () {\n return {\n type: 'Buffer',\n data: Array.prototype.slice.call(this._arr || this, 0)\n }\n}\n\nfunction base64Slice (buf, start, end) {\n if (start === 0 && end === buf.length) {\n return base64.fromByteArray(buf)\n } else {\n return base64.fromByteArray(buf.slice(start, end))\n }\n}\n\nfunction utf8Slice (buf, start, end) {\n end = Math.min(buf.length, end)\n var res = []\n\n var i = start\n while (i < end) {\n var firstByte = buf[i]\n var codePoint = null\n var bytesPerSequence = (firstByte > 0xEF) ? 4\n : (firstByte > 0xDF) ? 3\n : (firstByte > 0xBF) ? 2\n : 1\n\n if (i + bytesPerSequence <= end) {\n var secondByte, thirdByte, fourthByte, tempCodePoint\n\n switch (bytesPerSequence) {\n case 1:\n if (firstByte < 0x80) {\n codePoint = firstByte\n }\n break\n case 2:\n secondByte = buf[i + 1]\n if ((secondByte & 0xC0) === 0x80) {\n tempCodePoint = (firstByte & 0x1F) << 0x6 | (secondByte & 0x3F)\n if (tempCodePoint > 0x7F) {\n codePoint = tempCodePoint\n }\n }\n break\n case 3:\n secondByte = buf[i + 1]\n thirdByte = buf[i + 2]\n if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80) {\n tempCodePoint = (firstByte & 0xF) << 0xC | (secondByte & 0x3F) << 0x6 | (thirdByte & 0x3F)\n if (tempCodePoint > 0x7FF && (tempCodePoint < 0xD800 || tempCodePoint > 0xDFFF)) {\n codePoint = tempCodePoint\n }\n }\n break\n case 4:\n secondByte = buf[i + 1]\n thirdByte = buf[i + 2]\n fourthByte = buf[i + 3]\n if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80 && (fourthByte & 0xC0) === 0x80) {\n tempCodePoint = (firstByte & 0xF) << 0x12 | (secondByte & 0x3F) << 0xC | (thirdByte & 0x3F) << 0x6 | (fourthByte & 0x3F)\n if (tempCodePoint > 0xFFFF && tempCodePoint < 0x110000) {\n codePoint = tempCodePoint\n }\n }\n }\n }\n\n if (codePoint === null) {\n // we did not generate a valid codePoint so insert a\n // replacement char (U+FFFD) and advance only 1 byte\n codePoint = 0xFFFD\n bytesPerSequence = 1\n } else if (codePoint > 0xFFFF) {\n // encode to utf16 (surrogate pair dance)\n codePoint -= 0x10000\n res.push(codePoint >>> 10 & 0x3FF | 0xD800)\n codePoint = 0xDC00 | codePoint & 0x3FF\n }\n\n res.push(codePoint)\n i += bytesPerSequence\n }\n\n return decodeCodePointsArray(res)\n}\n\n// Based on http://stackoverflow.com/a/22747272/680742, the browser with\n// the lowest limit is Chrome, with 0x10000 args.\n// We go 1 magnitude less, for safety\nvar MAX_ARGUMENTS_LENGTH = 0x1000\n\nfunction decodeCodePointsArray (codePoints) {\n var len = codePoints.length\n if (len <= MAX_ARGUMENTS_LENGTH) {\n return String.fromCharCode.apply(String, codePoints) // avoid extra slice()\n }\n\n // Decode in chunks to avoid \"call stack size exceeded\".\n var res = ''\n var i = 0\n while (i < len) {\n res += String.fromCharCode.apply(\n String,\n codePoints.slice(i, i += MAX_ARGUMENTS_LENGTH)\n )\n }\n return res\n}\n\nfunction asciiSlice (buf, start, end) {\n var ret = ''\n end = Math.min(buf.length, end)\n\n for (var i = start; i < end; ++i) {\n ret += String.fromCharCode(buf[i] & 0x7F)\n }\n return ret\n}\n\nfunction latin1Slice (buf, start, end) {\n var ret = ''\n end = Math.min(buf.length, end)\n\n for (var i = start; i < end; ++i) {\n ret += String.fromCharCode(buf[i])\n }\n return ret\n}\n\nfunction hexSlice (buf, start, end) {\n var len = buf.length\n\n if (!start || start < 0) start = 0\n if (!end || end < 0 || end > len) end = len\n\n var out = ''\n for (var i = start; i < end; ++i) {\n out += toHex(buf[i])\n }\n return out\n}\n\nfunction utf16leSlice (buf, start, end) {\n var bytes = buf.slice(start, end)\n var res = ''\n for (var i = 0; i < bytes.length; i += 2) {\n res += String.fromCharCode(bytes[i] + bytes[i + 1] * 256)\n }\n return res\n}\n\nBuffer.prototype.slice = function slice (start, end) {\n var len = this.length\n start = ~~start\n end = end === undefined ? len : ~~end\n\n if (start < 0) {\n start += len\n if (start < 0) start = 0\n } else if (start > len) {\n start = len\n }\n\n if (end < 0) {\n end += len\n if (end < 0) end = 0\n } else if (end > len) {\n end = len\n }\n\n if (end < start) end = start\n\n var newBuf\n if (Buffer.TYPED_ARRAY_SUPPORT) {\n newBuf = this.subarray(start, end)\n newBuf.__proto__ = Buffer.prototype\n } else {\n var sliceLen = end - start\n newBuf = new Buffer(sliceLen, undefined)\n for (var i = 0; i < sliceLen; ++i) {\n newBuf[i] = this[i + start]\n }\n }\n\n return newBuf\n}\n\n/*\n * Need to make sure that buffer isn't trying to write out of bounds.\n */\nfunction checkOffset (offset, ext, length) {\n if ((offset % 1) !== 0 || offset < 0) throw new RangeError('offset is not uint')\n if (offset + ext > length) throw new RangeError('Trying to access beyond buffer length')\n}\n\nBuffer.prototype.readUIntLE = function readUIntLE (offset, byteLength, noAssert) {\n offset = offset | 0\n byteLength = byteLength | 0\n if (!noAssert) checkOffset(offset, byteLength, this.length)\n\n var val = this[offset]\n var mul = 1\n var i = 0\n while (++i < byteLength && (mul *= 0x100)) {\n val += this[offset + i] * mul\n }\n\n return val\n}\n\nBuffer.prototype.readUIntBE = function readUIntBE (offset, byteLength, noAssert) {\n offset = offset | 0\n byteLength = byteLength | 0\n if (!noAssert) {\n checkOffset(offset, byteLength, this.length)\n }\n\n var val = this[offset + --byteLength]\n var mul = 1\n while (byteLength > 0 && (mul *= 0x100)) {\n val += this[offset + --byteLength] * mul\n }\n\n return val\n}\n\nBuffer.prototype.readUInt8 = function readUInt8 (offset, noAssert) {\n if (!noAssert) checkOffset(offset, 1, this.length)\n return this[offset]\n}\n\nBuffer.prototype.readUInt16LE = function readUInt16LE (offset, noAssert) {\n if (!noAssert) checkOffset(offset, 2, this.length)\n return this[offset] | (this[offset + 1] << 8)\n}\n\nBuffer.prototype.readUInt16BE = function readUInt16BE (offset, noAssert) {\n if (!noAssert) checkOffset(offset, 2, this.length)\n return (this[offset] << 8) | this[offset + 1]\n}\n\nBuffer.prototype.readUInt32LE = function readUInt32LE (offset, noAssert) {\n if (!noAssert) checkOffset(offset, 4, this.length)\n\n return ((this[offset]) |\n (this[offset + 1] << 8) |\n (this[offset + 2] << 16)) +\n (this[offset + 3] * 0x1000000)\n}\n\nBuffer.prototype.readUInt32BE = function readUInt32BE (offset, noAssert) {\n if (!noAssert) checkOffset(offset, 4, this.length)\n\n return (this[offset] * 0x1000000) +\n ((this[offset + 1] << 16) |\n (this[offset + 2] << 8) |\n this[offset + 3])\n}\n\nBuffer.prototype.readIntLE = function readIntLE (offset, byteLength, noAssert) {\n offset = offset | 0\n byteLength = byteLength | 0\n if (!noAssert) checkOffset(offset, byteLength, this.length)\n\n var val = this[offset]\n var mul = 1\n var i = 0\n while (++i < byteLength && (mul *= 0x100)) {\n val += this[offset + i] * mul\n }\n mul *= 0x80\n\n if (val >= mul) val -= Math.pow(2, 8 * byteLength)\n\n return val\n}\n\nBuffer.prototype.readIntBE = function readIntBE (offset, byteLength, noAssert) {\n offset = offset | 0\n byteLength = byteLength | 0\n if (!noAssert) checkOffset(offset, byteLength, this.length)\n\n var i = byteLength\n var mul = 1\n var val = this[offset + --i]\n while (i > 0 && (mul *= 0x100)) {\n val += this[offset + --i] * mul\n }\n mul *= 0x80\n\n if (val >= mul) val -= Math.pow(2, 8 * byteLength)\n\n return val\n}\n\nBuffer.prototype.readInt8 = function readInt8 (offset, noAssert) {\n if (!noAssert) checkOffset(offset, 1, this.length)\n if (!(this[offset] & 0x80)) return (this[offset])\n return ((0xff - this[offset] + 1) * -1)\n}\n\nBuffer.prototype.readInt16LE = function readInt16LE (offset, noAssert) {\n if (!noAssert) checkOffset(offset, 2, this.length)\n var val = this[offset] | (this[offset + 1] << 8)\n return (val & 0x8000) ? val | 0xFFFF0000 : val\n}\n\nBuffer.prototype.readInt16BE = function readInt16BE (offset, noAssert) {\n if (!noAssert) checkOffset(offset, 2, this.length)\n var val = this[offset + 1] | (this[offset] << 8)\n return (val & 0x8000) ? val | 0xFFFF0000 : val\n}\n\nBuffer.prototype.readInt32LE = function readInt32LE (offset, noAssert) {\n if (!noAssert) checkOffset(offset, 4, this.length)\n\n return (this[offset]) |\n (this[offset + 1] << 8) |\n (this[offset + 2] << 16) |\n (this[offset + 3] << 24)\n}\n\nBuffer.prototype.readInt32BE = function readInt32BE (offset, noAssert) {\n if (!noAssert) checkOffset(offset, 4, this.length)\n\n return (this[offset] << 24) |\n (this[offset + 1] << 16) |\n (this[offset + 2] << 8) |\n (this[offset + 3])\n}\n\nBuffer.prototype.readFloatLE = function readFloatLE (offset, noAssert) {\n if (!noAssert) checkOffset(offset, 4, this.length)\n return ieee754.read(this, offset, true, 23, 4)\n}\n\nBuffer.prototype.readFloatBE = function readFloatBE (offset, noAssert) {\n if (!noAssert) checkOffset(offset, 4, this.length)\n return ieee754.read(this, offset, false, 23, 4)\n}\n\nBuffer.prototype.readDoubleLE = function readDoubleLE (offset, noAssert) {\n if (!noAssert) checkOffset(offset, 8, this.length)\n return ieee754.read(this, offset, true, 52, 8)\n}\n\nBuffer.prototype.readDoubleBE = function readDoubleBE (offset, noAssert) {\n if (!noAssert) checkOffset(offset, 8, this.length)\n return ieee754.read(this, offset, false, 52, 8)\n}\n\nfunction checkInt (buf, value, offset, ext, max, min) {\n if (!Buffer.isBuffer(buf)) throw new TypeError('\"buffer\" argument must be a Buffer instance')\n if (value > max || value < min) throw new RangeError('\"value\" argument is out of bounds')\n if (offset + ext > buf.length) throw new RangeError('Index out of range')\n}\n\nBuffer.prototype.writeUIntLE = function writeUIntLE (value, offset, byteLength, noAssert) {\n value = +value\n offset = offset | 0\n byteLength = byteLength | 0\n if (!noAssert) {\n var maxBytes = Math.pow(2, 8 * byteLength) - 1\n checkInt(this, value, offset, byteLength, maxBytes, 0)\n }\n\n var mul = 1\n var i = 0\n this[offset] = value & 0xFF\n while (++i < byteLength && (mul *= 0x100)) {\n this[offset + i] = (value / mul) & 0xFF\n }\n\n return offset + byteLength\n}\n\nBuffer.prototype.writeUIntBE = function writeUIntBE (value, offset, byteLength, noAssert) {\n value = +value\n offset = offset | 0\n byteLength = byteLength | 0\n if (!noAssert) {\n var maxBytes = Math.pow(2, 8 * byteLength) - 1\n checkInt(this, value, offset, byteLength, maxBytes, 0)\n }\n\n var i = byteLength - 1\n var mul = 1\n this[offset + i] = value & 0xFF\n while (--i >= 0 && (mul *= 0x100)) {\n this[offset + i] = (value / mul) & 0xFF\n }\n\n return offset + byteLength\n}\n\nBuffer.prototype.writeUInt8 = function writeUInt8 (value, offset, noAssert) {\n value = +value\n offset = offset | 0\n if (!noAssert) checkInt(this, value, offset, 1, 0xff, 0)\n if (!Buffer.TYPED_ARRAY_SUPPORT) value = Math.floor(value)\n this[offset] = (value & 0xff)\n return offset + 1\n}\n\nfunction objectWriteUInt16 (buf, value, offset, littleEndian) {\n if (value < 0) value = 0xffff + value + 1\n for (var i = 0, j = Math.min(buf.length - offset, 2); i < j; ++i) {\n buf[offset + i] = (value & (0xff << (8 * (littleEndian ? i : 1 - i)))) >>>\n (littleEndian ? i : 1 - i) * 8\n }\n}\n\nBuffer.prototype.writeUInt16LE = function writeUInt16LE (value, offset, noAssert) {\n value = +value\n offset = offset | 0\n if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0)\n if (Buffer.TYPED_ARRAY_SUPPORT) {\n this[offset] = (value & 0xff)\n this[offset + 1] = (value >>> 8)\n } else {\n objectWriteUInt16(this, value, offset, true)\n }\n return offset + 2\n}\n\nBuffer.prototype.writeUInt16BE = function writeUInt16BE (value, offset, noAssert) {\n value = +value\n offset = offset | 0\n if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0)\n if (Buffer.TYPED_ARRAY_SUPPORT) {\n this[offset] = (value >>> 8)\n this[offset + 1] = (value & 0xff)\n } else {\n objectWriteUInt16(this, value, offset, false)\n }\n return offset + 2\n}\n\nfunction objectWriteUInt32 (buf, value, offset, littleEndian) {\n if (value < 0) value = 0xffffffff + value + 1\n for (var i = 0, j = Math.min(buf.length - offset, 4); i < j; ++i) {\n buf[offset + i] = (value >>> (littleEndian ? i : 3 - i) * 8) & 0xff\n }\n}\n\nBuffer.prototype.writeUInt32LE = function writeUInt32LE (value, offset, noAssert) {\n value = +value\n offset = offset | 0\n if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0)\n if (Buffer.TYPED_ARRAY_SUPPORT) {\n this[offset + 3] = (value >>> 24)\n this[offset + 2] = (value >>> 16)\n this[offset + 1] = (value >>> 8)\n this[offset] = (value & 0xff)\n } else {\n objectWriteUInt32(this, value, offset, true)\n }\n return offset + 4\n}\n\nBuffer.prototype.writeUInt32BE = function writeUInt32BE (value, offset, noAssert) {\n value = +value\n offset = offset | 0\n if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0)\n if (Buffer.TYPED_ARRAY_SUPPORT) {\n this[offset] = (value >>> 24)\n this[offset + 1] = (value >>> 16)\n this[offset + 2] = (value >>> 8)\n this[offset + 3] = (value & 0xff)\n } else {\n objectWriteUInt32(this, value, offset, false)\n }\n return offset + 4\n}\n\nBuffer.prototype.writeIntLE = function writeIntLE (value, offset, byteLength, noAssert) {\n value = +value\n offset = offset | 0\n if (!noAssert) {\n var limit = Math.pow(2, 8 * byteLength - 1)\n\n checkInt(this, value, offset, byteLength, limit - 1, -limit)\n }\n\n var i = 0\n var mul = 1\n var sub = 0\n this[offset] = value & 0xFF\n while (++i < byteLength && (mul *= 0x100)) {\n if (value < 0 && sub === 0 && this[offset + i - 1] !== 0) {\n sub = 1\n }\n this[offset + i] = ((value / mul) >> 0) - sub & 0xFF\n }\n\n return offset + byteLength\n}\n\nBuffer.prototype.writeIntBE = function writeIntBE (value, offset, byteLength, noAssert) {\n value = +value\n offset = offset | 0\n if (!noAssert) {\n var limit = Math.pow(2, 8 * byteLength - 1)\n\n checkInt(this, value, offset, byteLength, limit - 1, -limit)\n }\n\n var i = byteLength - 1\n var mul = 1\n var sub = 0\n this[offset + i] = value & 0xFF\n while (--i >= 0 && (mul *= 0x100)) {\n if (value < 0 && sub === 0 && this[offset + i + 1] !== 0) {\n sub = 1\n }\n this[offset + i] = ((value / mul) >> 0) - sub & 0xFF\n }\n\n return offset + byteLength\n}\n\nBuffer.prototype.writeInt8 = function writeInt8 (value, offset, noAssert) {\n value = +value\n offset = offset | 0\n if (!noAssert) checkInt(this, value, offset, 1, 0x7f, -0x80)\n if (!Buffer.TYPED_ARRAY_SUPPORT) value = Math.floor(value)\n if (value < 0) value = 0xff + value + 1\n this[offset] = (value & 0xff)\n return offset + 1\n}\n\nBuffer.prototype.writeInt16LE = function writeInt16LE (value, offset, noAssert) {\n value = +value\n offset = offset | 0\n if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000)\n if (Buffer.TYPED_ARRAY_SUPPORT) {\n this[offset] = (value & 0xff)\n this[offset + 1] = (value >>> 8)\n } else {\n objectWriteUInt16(this, value, offset, true)\n }\n return offset + 2\n}\n\nBuffer.prototype.writeInt16BE = function writeInt16BE (value, offset, noAssert) {\n value = +value\n offset = offset | 0\n if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000)\n if (Buffer.TYPED_ARRAY_SUPPORT) {\n this[offset] = (value >>> 8)\n this[offset + 1] = (value & 0xff)\n } else {\n objectWriteUInt16(this, value, offset, false)\n }\n return offset + 2\n}\n\nBuffer.prototype.writeInt32LE = function writeInt32LE (value, offset, noAssert) {\n value = +value\n offset = offset | 0\n if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000)\n if (Buffer.TYPED_ARRAY_SUPPORT) {\n this[offset] = (value & 0xff)\n this[offset + 1] = (value >>> 8)\n this[offset + 2] = (value >>> 16)\n this[offset + 3] = (value >>> 24)\n } else {\n objectWriteUInt32(this, value, offset, true)\n }\n return offset + 4\n}\n\nBuffer.prototype.writeInt32BE = function writeInt32BE (value, offset, noAssert) {\n value = +value\n offset = offset | 0\n if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000)\n if (value < 0) value = 0xffffffff + value + 1\n if (Buffer.TYPED_ARRAY_SUPPORT) {\n this[offset] = (value >>> 24)\n this[offset + 1] = (value >>> 16)\n this[offset + 2] = (value >>> 8)\n this[offset + 3] = (value & 0xff)\n } else {\n objectWriteUInt32(this, value, offset, false)\n }\n return offset + 4\n}\n\nfunction checkIEEE754 (buf, value, offset, ext, max, min) {\n if (offset + ext > buf.length) throw new RangeError('Index out of range')\n if (offset < 0) throw new RangeError('Index out of range')\n}\n\nfunction writeFloat (buf, value, offset, littleEndian, noAssert) {\n if (!noAssert) {\n checkIEEE754(buf, value, offset, 4, 3.4028234663852886e+38, -3.4028234663852886e+38)\n }\n ieee754.write(buf, value, offset, littleEndian, 23, 4)\n return offset + 4\n}\n\nBuffer.prototype.writeFloatLE = function writeFloatLE (value, offset, noAssert) {\n return writeFloat(this, value, offset, true, noAssert)\n}\n\nBuffer.prototype.writeFloatBE = function writeFloatBE (value, offset, noAssert) {\n return writeFloat(this, value, offset, false, noAssert)\n}\n\nfunction writeDouble (buf, value, offset, littleEndian, noAssert) {\n if (!noAssert) {\n checkIEEE754(buf, value, offset, 8, 1.7976931348623157E+308, -1.7976931348623157E+308)\n }\n ieee754.write(buf, value, offset, littleEndian, 52, 8)\n return offset + 8\n}\n\nBuffer.prototype.writeDoubleLE = function writeDoubleLE (value, offset, noAssert) {\n return writeDouble(this, value, offset, true, noAssert)\n}\n\nBuffer.prototype.writeDoubleBE = function writeDoubleBE (value, offset, noAssert) {\n return writeDouble(this, value, offset, false, noAssert)\n}\n\n// copy(targetBuffer, targetStart=0, sourceStart=0, sourceEnd=buffer.length)\nBuffer.prototype.copy = function copy (target, targetStart, start, end) {\n if (!start) start = 0\n if (!end && end !== 0) end = this.length\n if (targetStart >= target.length) targetStart = target.length\n if (!targetStart) targetStart = 0\n if (end > 0 && end < start) end = start\n\n // Copy 0 bytes; we're done\n if (end === start) return 0\n if (target.length === 0 || this.length === 0) return 0\n\n // Fatal error conditions\n if (targetStart < 0) {\n throw new RangeError('targetStart out of bounds')\n }\n if (start < 0 || start >= this.length) throw new RangeError('sourceStart out of bounds')\n if (end < 0) throw new RangeError('sourceEnd out of bounds')\n\n // Are we oob?\n if (end > this.length) end = this.length\n if (target.length - targetStart < end - start) {\n end = target.length - targetStart + start\n }\n\n var len = end - start\n var i\n\n if (this === target && start < targetStart && targetStart < end) {\n // descending copy from end\n for (i = len - 1; i >= 0; --i) {\n target[i + targetStart] = this[i + start]\n }\n } else if (len < 1000 || !Buffer.TYPED_ARRAY_SUPPORT) {\n // ascending copy from start\n for (i = 0; i < len; ++i) {\n target[i + targetStart] = this[i + start]\n }\n } else {\n Uint8Array.prototype.set.call(\n target,\n this.subarray(start, start + len),\n targetStart\n )\n }\n\n return len\n}\n\n// Usage:\n// buffer.fill(number[, offset[, end]])\n// buffer.fill(buffer[, offset[, end]])\n// buffer.fill(string[, offset[, end]][, encoding])\nBuffer.prototype.fill = function fill (val, start, end, encoding) {\n // Handle string cases:\n if (typeof val === 'string') {\n if (typeof start === 'string') {\n encoding = start\n start = 0\n end = this.length\n } else if (typeof end === 'string') {\n encoding = end\n end = this.length\n }\n if (val.length === 1) {\n var code = val.charCodeAt(0)\n if (code < 256) {\n val = code\n }\n }\n if (encoding !== undefined && typeof encoding !== 'string') {\n throw new TypeError('encoding must be a string')\n }\n if (typeof encoding === 'string' && !Buffer.isEncoding(encoding)) {\n throw new TypeError('Unknown encoding: ' + encoding)\n }\n } else if (typeof val === 'number') {\n val = val & 255\n }\n\n // Invalid ranges are not set to a default, so can range check early.\n if (start < 0 || this.length < start || this.length < end) {\n throw new RangeError('Out of range index')\n }\n\n if (end <= start) {\n return this\n }\n\n start = start >>> 0\n end = end === undefined ? this.length : end >>> 0\n\n if (!val) val = 0\n\n var i\n if (typeof val === 'number') {\n for (i = start; i < end; ++i) {\n this[i] = val\n }\n } else {\n var bytes = Buffer.isBuffer(val)\n ? val\n : utf8ToBytes(new Buffer(val, encoding).toString())\n var len = bytes.length\n for (i = 0; i < end - start; ++i) {\n this[i + start] = bytes[i % len]\n }\n }\n\n return this\n}\n\n// HELPER FUNCTIONS\n// ================\n\nvar INVALID_BASE64_RE = /[^+\\/0-9A-Za-z-_]/g\n\nfunction base64clean (str) {\n // Node strips out invalid characters like \\n and \\t from the string, base64-js does not\n str = stringtrim(str).replace(INVALID_BASE64_RE, '')\n // Node converts strings with length < 2 to ''\n if (str.length < 2) return ''\n // Node allows for non-padded base64 strings (missing trailing ===), base64-js does not\n while (str.length % 4 !== 0) {\n str = str + '='\n }\n return str\n}\n\nfunction stringtrim (str) {\n if (str.trim) return str.trim()\n return str.replace(/^\\s+|\\s+$/g, '')\n}\n\nfunction toHex (n) {\n if (n < 16) return '0' + n.toString(16)\n return n.toString(16)\n}\n\nfunction utf8ToBytes (string, units) {\n units = units || Infinity\n var codePoint\n var length = string.length\n var leadSurrogate = null\n var bytes = []\n\n for (var i = 0; i < length; ++i) {\n codePoint = string.charCodeAt(i)\n\n // is surrogate component\n if (codePoint > 0xD7FF && codePoint < 0xE000) {\n // last char was a lead\n if (!leadSurrogate) {\n // no lead yet\n if (codePoint > 0xDBFF) {\n // unexpected trail\n if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)\n continue\n } else if (i + 1 === length) {\n // unpaired lead\n if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)\n continue\n }\n\n // valid lead\n leadSurrogate = codePoint\n\n continue\n }\n\n // 2 leads in a row\n if (codePoint < 0xDC00) {\n if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)\n leadSurrogate = codePoint\n continue\n }\n\n // valid surrogate pair\n codePoint = (leadSurrogate - 0xD800 << 10 | codePoint - 0xDC00) + 0x10000\n } else if (leadSurrogate) {\n // valid bmp char, but last char was a lead\n if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)\n }\n\n leadSurrogate = null\n\n // encode utf8\n if (codePoint < 0x80) {\n if ((units -= 1) < 0) break\n bytes.push(codePoint)\n } else if (codePoint < 0x800) {\n if ((units -= 2) < 0) break\n bytes.push(\n codePoint >> 0x6 | 0xC0,\n codePoint & 0x3F | 0x80\n )\n } else if (codePoint < 0x10000) {\n if ((units -= 3) < 0) break\n bytes.push(\n codePoint >> 0xC | 0xE0,\n codePoint >> 0x6 & 0x3F | 0x80,\n codePoint & 0x3F | 0x80\n )\n } else if (codePoint < 0x110000) {\n if ((units -= 4) < 0) break\n bytes.push(\n codePoint >> 0x12 | 0xF0,\n codePoint >> 0xC & 0x3F | 0x80,\n codePoint >> 0x6 & 0x3F | 0x80,\n codePoint & 0x3F | 0x80\n )\n } else {\n throw new Error('Invalid code point')\n }\n }\n\n return bytes\n}\n\nfunction asciiToBytes (str) {\n var byteArray = []\n for (var i = 0; i < str.length; ++i) {\n // Node's code seems to be doing this and not & 0x7F..\n byteArray.push(str.charCodeAt(i) & 0xFF)\n }\n return byteArray\n}\n\nfunction utf16leToBytes (str, units) {\n var c, hi, lo\n var byteArray = []\n for (var i = 0; i < str.length; ++i) {\n if ((units -= 2) < 0) break\n\n c = str.charCodeAt(i)\n hi = c >> 8\n lo = c % 256\n byteArray.push(lo)\n byteArray.push(hi)\n }\n\n return byteArray\n}\n\nfunction base64ToBytes (str) {\n return base64.toByteArray(base64clean(str))\n}\n\nfunction blitBuffer (src, dst, offset, length) {\n for (var i = 0; i < length; ++i) {\n if ((i + offset >= dst.length) || (i >= src.length)) break\n dst[i + offset] = src[i]\n }\n return i\n}\n\nfunction isnan (val) {\n return val !== val // eslint-disable-line no-self-compare\n}\n","'use strict'\n\nexports.byteLength = byteLength\nexports.toByteArray = toByteArray\nexports.fromByteArray = fromByteArray\n\nvar lookup = []\nvar revLookup = []\nvar Arr = typeof Uint8Array !== 'undefined' ? Uint8Array : Array\n\nvar code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'\nfor (var i = 0, len = code.length; i < len; ++i) {\n lookup[i] = code[i]\n revLookup[code.charCodeAt(i)] = i\n}\n\n// Support decoding URL-safe base64 strings, as Node.js does.\n// See: https://en.wikipedia.org/wiki/Base64#URL_applications\nrevLookup['-'.charCodeAt(0)] = 62\nrevLookup['_'.charCodeAt(0)] = 63\n\nfunction getLens (b64) {\n var len = b64.length\n\n if (len % 4 > 0) {\n throw new Error('Invalid string. Length must be a multiple of 4')\n }\n\n // Trim off extra bytes after placeholder bytes are found\n // See: https://github.com/beatgammit/base64-js/issues/42\n var validLen = b64.indexOf('=')\n if (validLen === -1) validLen = len\n\n var placeHoldersLen = validLen === len\n ? 0\n : 4 - (validLen % 4)\n\n return [validLen, placeHoldersLen]\n}\n\n// base64 is 4/3 + up to two characters of the original data\nfunction byteLength (b64) {\n var lens = getLens(b64)\n var validLen = lens[0]\n var placeHoldersLen = lens[1]\n return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen\n}\n\nfunction _byteLength (b64, validLen, placeHoldersLen) {\n return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen\n}\n\nfunction toByteArray (b64) {\n var tmp\n var lens = getLens(b64)\n var validLen = lens[0]\n var placeHoldersLen = lens[1]\n\n var arr = new Arr(_byteLength(b64, validLen, placeHoldersLen))\n\n var curByte = 0\n\n // if there are placeholders, only get up to the last complete 4 chars\n var len = placeHoldersLen > 0\n ? validLen - 4\n : validLen\n\n for (var i = 0; i < len; i += 4) {\n tmp =\n (revLookup[b64.charCodeAt(i)] << 18) |\n (revLookup[b64.charCodeAt(i + 1)] << 12) |\n (revLookup[b64.charCodeAt(i + 2)] << 6) |\n revLookup[b64.charCodeAt(i + 3)]\n arr[curByte++] = (tmp >> 16) & 0xFF\n arr[curByte++] = (tmp >> 8) & 0xFF\n arr[curByte++] = tmp & 0xFF\n }\n\n if (placeHoldersLen === 2) {\n tmp =\n (revLookup[b64.charCodeAt(i)] << 2) |\n (revLookup[b64.charCodeAt(i + 1)] >> 4)\n arr[curByte++] = tmp & 0xFF\n }\n\n if (placeHoldersLen === 1) {\n tmp =\n (revLookup[b64.charCodeAt(i)] << 10) |\n (revLookup[b64.charCodeAt(i + 1)] << 4) |\n (revLookup[b64.charCodeAt(i + 2)] >> 2)\n arr[curByte++] = (tmp >> 8) & 0xFF\n arr[curByte++] = tmp & 0xFF\n }\n\n return arr\n}\n\nfunction tripletToBase64 (num) {\n return lookup[num >> 18 & 0x3F] +\n lookup[num >> 12 & 0x3F] +\n lookup[num >> 6 & 0x3F] +\n lookup[num & 0x3F]\n}\n\nfunction encodeChunk (uint8, start, end) {\n var tmp\n var output = []\n for (var i = start; i < end; i += 3) {\n tmp =\n ((uint8[i] << 16) & 0xFF0000) +\n ((uint8[i + 1] << 8) & 0xFF00) +\n (uint8[i + 2] & 0xFF)\n output.push(tripletToBase64(tmp))\n }\n return output.join('')\n}\n\nfunction fromByteArray (uint8) {\n var tmp\n var len = uint8.length\n var extraBytes = len % 3 // if we have 1 byte left, pad 2 bytes\n var parts = []\n var maxChunkLength = 16383 // must be multiple of 3\n\n // go through the array every three bytes, we'll deal with trailing stuff later\n for (var i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) {\n parts.push(encodeChunk(\n uint8, i, (i + maxChunkLength) > len2 ? len2 : (i + maxChunkLength)\n ))\n }\n\n // pad the end with zeros, but make sure to not forget the extra bytes\n if (extraBytes === 1) {\n tmp = uint8[len - 1]\n parts.push(\n lookup[tmp >> 2] +\n lookup[(tmp << 4) & 0x3F] +\n '=='\n )\n } else if (extraBytes === 2) {\n tmp = (uint8[len - 2] << 8) + uint8[len - 1]\n parts.push(\n lookup[tmp >> 10] +\n lookup[(tmp >> 4) & 0x3F] +\n lookup[(tmp << 2) & 0x3F] +\n '='\n )\n }\n\n return parts.join('')\n}\n","exports.read = function (buffer, offset, isLE, mLen, nBytes) {\n var e, m\n var eLen = (nBytes * 8) - mLen - 1\n var eMax = (1 << eLen) - 1\n var eBias = eMax >> 1\n var nBits = -7\n var i = isLE ? (nBytes - 1) : 0\n var d = isLE ? -1 : 1\n var s = buffer[offset + i]\n\n i += d\n\n e = s & ((1 << (-nBits)) - 1)\n s >>= (-nBits)\n nBits += eLen\n for (; nBits > 0; e = (e * 256) + buffer[offset + i], i += d, nBits -= 8) {}\n\n m = e & ((1 << (-nBits)) - 1)\n e >>= (-nBits)\n nBits += mLen\n for (; nBits > 0; m = (m * 256) + buffer[offset + i], i += d, nBits -= 8) {}\n\n if (e === 0) {\n e = 1 - eBias\n } else if (e === eMax) {\n return m ? NaN : ((s ? -1 : 1) * Infinity)\n } else {\n m = m + Math.pow(2, mLen)\n e = e - eBias\n }\n return (s ? -1 : 1) * m * Math.pow(2, e - mLen)\n}\n\nexports.write = function (buffer, value, offset, isLE, mLen, nBytes) {\n var e, m, c\n var eLen = (nBytes * 8) - mLen - 1\n var eMax = (1 << eLen) - 1\n var eBias = eMax >> 1\n var rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0)\n var i = isLE ? 0 : (nBytes - 1)\n var d = isLE ? 1 : -1\n var s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0\n\n value = Math.abs(value)\n\n if (isNaN(value) || value === Infinity) {\n m = isNaN(value) ? 1 : 0\n e = eMax\n } else {\n e = Math.floor(Math.log(value) / Math.LN2)\n if (value * (c = Math.pow(2, -e)) < 1) {\n e--\n c *= 2\n }\n if (e + eBias >= 1) {\n value += rt / c\n } else {\n value += rt * Math.pow(2, 1 - eBias)\n }\n if (value * c >= 2) {\n e++\n c /= 2\n }\n\n if (e + eBias >= eMax) {\n m = 0\n e = eMax\n } else if (e + eBias >= 1) {\n m = ((value * c) - 1) * Math.pow(2, mLen)\n e = e + eBias\n } else {\n m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen)\n e = 0\n }\n }\n\n for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {}\n\n e = (e << mLen) | m\n eLen += mLen\n for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {}\n\n buffer[offset + i - d] |= s * 128\n}\n","var toString = {}.toString;\n\nmodule.exports = Array.isArray || function (arr) {\n return toString.call(arr) == '[object Array]';\n};\n","/*\r\nAuthor: Geraint Luff and others\r\nYear: 2013\r\n\r\nThis code is released into the \"public domain\" by its author(s). Anybody may use, alter and distribute the code without restriction. The author makes no guarantees, and takes no liability of any kind for use of this code.\r\n\r\nIf you find a bug or make an improvement, it would be courteous to let the author know, but it is not compulsory.\r\n*/\r\n(function (global, factory) {\r\n if (typeof define === 'function' && define.amd) {\r\n // AMD. Register as an anonymous module.\r\n define([], factory);\r\n } else if (typeof module !== 'undefined' && module.exports){\r\n // CommonJS. Define export.\r\n module.exports = factory();\r\n } else {\r\n // Browser globals\r\n global.tv4 = factory();\r\n }\r\n}(this, function () {\r\n\r\n// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys?redirectlocale=en-US&redirectslug=JavaScript%2FReference%2FGlobal_Objects%2FObject%2Fkeys\r\nif (!Object.keys) {\r\n\tObject.keys = (function () {\r\n\t\tvar hasOwnProperty = Object.prototype.hasOwnProperty,\r\n\t\t\thasDontEnumBug = !({toString: null}).propertyIsEnumerable('toString'),\r\n\t\t\tdontEnums = [\r\n\t\t\t\t'toString',\r\n\t\t\t\t'toLocaleString',\r\n\t\t\t\t'valueOf',\r\n\t\t\t\t'hasOwnProperty',\r\n\t\t\t\t'isPrototypeOf',\r\n\t\t\t\t'propertyIsEnumerable',\r\n\t\t\t\t'constructor'\r\n\t\t\t],\r\n\t\t\tdontEnumsLength = dontEnums.length;\r\n\r\n\t\treturn function (obj) {\r\n\t\t\tif (typeof obj !== 'object' && typeof obj !== 'function' || obj === null) {\r\n\t\t\t\tthrow new TypeError('Object.keys called on non-object');\r\n\t\t\t}\r\n\r\n\t\t\tvar result = [];\r\n\r\n\t\t\tfor (var prop in obj) {\r\n\t\t\t\tif (hasOwnProperty.call(obj, prop)) {\r\n\t\t\t\t\tresult.push(prop);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tif (hasDontEnumBug) {\r\n\t\t\t\tfor (var i=0; i < dontEnumsLength; i++) {\r\n\t\t\t\t\tif (hasOwnProperty.call(obj, dontEnums[i])) {\r\n\t\t\t\t\t\tresult.push(dontEnums[i]);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\treturn result;\r\n\t\t};\r\n\t})();\r\n}\r\n// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create\r\nif (!Object.create) {\r\n\tObject.create = (function(){\r\n\t\tfunction F(){}\r\n\r\n\t\treturn function(o){\r\n\t\t\tif (arguments.length !== 1) {\r\n\t\t\t\tthrow new Error('Object.create implementation only accepts one parameter.');\r\n\t\t\t}\r\n\t\t\tF.prototype = o;\r\n\t\t\treturn new F();\r\n\t\t};\r\n\t})();\r\n}\r\n// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray?redirectlocale=en-US&redirectslug=JavaScript%2FReference%2FGlobal_Objects%2FArray%2FisArray\r\nif(!Array.isArray) {\r\n\tArray.isArray = function (vArg) {\r\n\t\treturn Object.prototype.toString.call(vArg) === \"[object Array]\";\r\n\t};\r\n}\r\n// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf?redirectlocale=en-US&redirectslug=JavaScript%2FReference%2FGlobal_Objects%2FArray%2FindexOf\r\nif (!Array.prototype.indexOf) {\r\n\tArray.prototype.indexOf = function (searchElement /*, fromIndex */ ) {\r\n\t\tif (this === null) {\r\n\t\t\tthrow new TypeError();\r\n\t\t}\r\n\t\tvar t = Object(this);\r\n\t\tvar len = t.length >>> 0;\r\n\r\n\t\tif (len === 0) {\r\n\t\t\treturn -1;\r\n\t\t}\r\n\t\tvar n = 0;\r\n\t\tif (arguments.length > 1) {\r\n\t\t\tn = Number(arguments[1]);\r\n\t\t\tif (n !== n) { // shortcut for verifying if it's NaN\r\n\t\t\t\tn = 0;\r\n\t\t\t} else if (n !== 0 && n !== Infinity && n !== -Infinity) {\r\n\t\t\t\tn = (n > 0 || -1) * Math.floor(Math.abs(n));\r\n\t\t\t}\r\n\t\t}\r\n\t\tif (n >= len) {\r\n\t\t\treturn -1;\r\n\t\t}\r\n\t\tvar k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);\r\n\t\tfor (; k < len; k++) {\r\n\t\t\tif (k in t && t[k] === searchElement) {\r\n\t\t\t\treturn k;\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn -1;\r\n\t};\r\n}\r\n\r\n// Grungey Object.isFrozen hack\r\nif (!Object.isFrozen) {\r\n\tObject.isFrozen = function (obj) {\r\n\t\tvar key = \"tv4_test_frozen_key\";\r\n\t\twhile (obj.hasOwnProperty(key)) {\r\n\t\t\tkey += Math.random();\r\n\t\t}\r\n\t\ttry {\r\n\t\t\tobj[key] = true;\r\n\t\t\tdelete obj[key];\r\n\t\t\treturn false;\r\n\t\t} catch (e) {\r\n\t\t\treturn true;\r\n\t\t}\r\n\t};\r\n}\r\n// Based on: https://github.com/geraintluff/uri-templates, but with all the de-substitution stuff removed\r\n\r\nvar uriTemplateGlobalModifiers = {\r\n\t\"+\": true,\r\n\t\"#\": true,\r\n\t\".\": true,\r\n\t\"/\": true,\r\n\t\";\": true,\r\n\t\"?\": true,\r\n\t\"&\": true\r\n};\r\nvar uriTemplateSuffices = {\r\n\t\"*\": true\r\n};\r\n\r\nfunction notReallyPercentEncode(string) {\r\n\treturn encodeURI(string).replace(/%25[0-9][0-9]/g, function (doubleEncoded) {\r\n\t\treturn \"%\" + doubleEncoded.substring(3);\r\n\t});\r\n}\r\n\r\nfunction uriTemplateSubstitution(spec) {\r\n\tvar modifier = \"\";\r\n\tif (uriTemplateGlobalModifiers[spec.charAt(0)]) {\r\n\t\tmodifier = spec.charAt(0);\r\n\t\tspec = spec.substring(1);\r\n\t}\r\n\tvar separator = \"\";\r\n\tvar prefix = \"\";\r\n\tvar shouldEscape = true;\r\n\tvar showVariables = false;\r\n\tvar trimEmptyString = false;\r\n\tif (modifier === '+') {\r\n\t\tshouldEscape = false;\r\n\t} else if (modifier === \".\") {\r\n\t\tprefix = \".\";\r\n\t\tseparator = \".\";\r\n\t} else if (modifier === \"/\") {\r\n\t\tprefix = \"/\";\r\n\t\tseparator = \"/\";\r\n\t} else if (modifier === '#') {\r\n\t\tprefix = \"#\";\r\n\t\tshouldEscape = false;\r\n\t} else if (modifier === ';') {\r\n\t\tprefix = \";\";\r\n\t\tseparator = \";\";\r\n\t\tshowVariables = true;\r\n\t\ttrimEmptyString = true;\r\n\t} else if (modifier === '?') {\r\n\t\tprefix = \"?\";\r\n\t\tseparator = \"&\";\r\n\t\tshowVariables = true;\r\n\t} else if (modifier === '&') {\r\n\t\tprefix = \"&\";\r\n\t\tseparator = \"&\";\r\n\t\tshowVariables = true;\r\n\t}\r\n\r\n\tvar varNames = [];\r\n\tvar varList = spec.split(\",\");\r\n\tvar varSpecs = [];\r\n\tvar varSpecMap = {};\r\n\tfor (var i = 0; i < varList.length; i++) {\r\n\t\tvar varName = varList[i];\r\n\t\tvar truncate = null;\r\n\t\tif (varName.indexOf(\":\") !== -1) {\r\n\t\t\tvar parts = varName.split(\":\");\r\n\t\t\tvarName = parts[0];\r\n\t\t\ttruncate = parseInt(parts[1], 10);\r\n\t\t}\r\n\t\tvar suffices = {};\r\n\t\twhile (uriTemplateSuffices[varName.charAt(varName.length - 1)]) {\r\n\t\t\tsuffices[varName.charAt(varName.length - 1)] = true;\r\n\t\t\tvarName = varName.substring(0, varName.length - 1);\r\n\t\t}\r\n\t\tvar varSpec = {\r\n\t\t\ttruncate: truncate,\r\n\t\t\tname: varName,\r\n\t\t\tsuffices: suffices\r\n\t\t};\r\n\t\tvarSpecs.push(varSpec);\r\n\t\tvarSpecMap[varName] = varSpec;\r\n\t\tvarNames.push(varName);\r\n\t}\r\n\tvar subFunction = function (valueFunction) {\r\n\t\tvar result = \"\";\r\n\t\tvar startIndex = 0;\r\n\t\tfor (var i = 0; i < varSpecs.length; i++) {\r\n\t\t\tvar varSpec = varSpecs[i];\r\n\t\t\tvar value = valueFunction(varSpec.name);\r\n\t\t\tif (value === null || value === undefined || (Array.isArray(value) && value.length === 0) || (typeof value === 'object' && Object.keys(value).length === 0)) {\r\n\t\t\t\tstartIndex++;\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\t\t\tif (i === startIndex) {\r\n\t\t\t\tresult += prefix;\r\n\t\t\t} else {\r\n\t\t\t\tresult += (separator || \",\");\r\n\t\t\t}\r\n\t\t\tif (Array.isArray(value)) {\r\n\t\t\t\tif (showVariables) {\r\n\t\t\t\t\tresult += varSpec.name + \"=\";\r\n\t\t\t\t}\r\n\t\t\t\tfor (var j = 0; j < value.length; j++) {\r\n\t\t\t\t\tif (j > 0) {\r\n\t\t\t\t\t\tresult += varSpec.suffices['*'] ? (separator || \",\") : \",\";\r\n\t\t\t\t\t\tif (varSpec.suffices['*'] && showVariables) {\r\n\t\t\t\t\t\t\tresult += varSpec.name + \"=\";\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\tresult += shouldEscape ? encodeURIComponent(value[j]).replace(/!/g, \"%21\") : notReallyPercentEncode(value[j]);\r\n\t\t\t\t}\r\n\t\t\t} else if (typeof value === \"object\") {\r\n\t\t\t\tif (showVariables && !varSpec.suffices['*']) {\r\n\t\t\t\t\tresult += varSpec.name + \"=\";\r\n\t\t\t\t}\r\n\t\t\t\tvar first = true;\r\n\t\t\t\tfor (var key in value) {\r\n\t\t\t\t\tif (!first) {\r\n\t\t\t\t\t\tresult += varSpec.suffices['*'] ? (separator || \",\") : \",\";\r\n\t\t\t\t\t}\r\n\t\t\t\t\tfirst = false;\r\n\t\t\t\t\tresult += shouldEscape ? encodeURIComponent(key).replace(/!/g, \"%21\") : notReallyPercentEncode(key);\r\n\t\t\t\t\tresult += varSpec.suffices['*'] ? '=' : \",\";\r\n\t\t\t\t\tresult += shouldEscape ? encodeURIComponent(value[key]).replace(/!/g, \"%21\") : notReallyPercentEncode(value[key]);\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\tif (showVariables) {\r\n\t\t\t\t\tresult += varSpec.name;\r\n\t\t\t\t\tif (!trimEmptyString || value !== \"\") {\r\n\t\t\t\t\t\tresult += \"=\";\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tif (varSpec.truncate != null) {\r\n\t\t\t\t\tvalue = value.substring(0, varSpec.truncate);\r\n\t\t\t\t}\r\n\t\t\t\tresult += shouldEscape ? encodeURIComponent(value).replace(/!/g, \"%21\"): notReallyPercentEncode(value);\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn result;\r\n\t};\r\n\tsubFunction.varNames = varNames;\r\n\treturn {\r\n\t\tprefix: prefix,\r\n\t\tsubstitution: subFunction\r\n\t};\r\n}\r\n\r\nfunction UriTemplate(template) {\r\n\tif (!(this instanceof UriTemplate)) {\r\n\t\treturn new UriTemplate(template);\r\n\t}\r\n\tvar parts = template.split(\"{\");\r\n\tvar textParts = [parts.shift()];\r\n\tvar prefixes = [];\r\n\tvar substitutions = [];\r\n\tvar varNames = [];\r\n\twhile (parts.length > 0) {\r\n\t\tvar part = parts.shift();\r\n\t\tvar spec = part.split(\"}\")[0];\r\n\t\tvar remainder = part.substring(spec.length + 1);\r\n\t\tvar funcs = uriTemplateSubstitution(spec);\r\n\t\tsubstitutions.push(funcs.substitution);\r\n\t\tprefixes.push(funcs.prefix);\r\n\t\ttextParts.push(remainder);\r\n\t\tvarNames = varNames.concat(funcs.substitution.varNames);\r\n\t}\r\n\tthis.fill = function (valueFunction) {\r\n\t\tvar result = textParts[0];\r\n\t\tfor (var i = 0; i < substitutions.length; i++) {\r\n\t\t\tvar substitution = substitutions[i];\r\n\t\t\tresult += substitution(valueFunction);\r\n\t\t\tresult += textParts[i + 1];\r\n\t\t}\r\n\t\treturn result;\r\n\t};\r\n\tthis.varNames = varNames;\r\n\tthis.template = template;\r\n}\r\nUriTemplate.prototype = {\r\n\ttoString: function () {\r\n\t\treturn this.template;\r\n\t},\r\n\tfillFromObject: function (obj) {\r\n\t\treturn this.fill(function (varName) {\r\n\t\t\treturn obj[varName];\r\n\t\t});\r\n\t}\r\n};\r\nvar ValidatorContext = function ValidatorContext(parent, collectMultiple, errorReporter, checkRecursive, trackUnknownProperties) {\r\n\tthis.missing = [];\r\n\tthis.missingMap = {};\r\n\tthis.formatValidators = parent ? Object.create(parent.formatValidators) : {};\r\n\tthis.schemas = parent ? Object.create(parent.schemas) : {};\r\n\tthis.collectMultiple = collectMultiple;\r\n\tthis.errors = [];\r\n\tthis.handleError = collectMultiple ? this.collectError : this.returnError;\r\n\tif (checkRecursive) {\r\n\t\tthis.checkRecursive = true;\r\n\t\tthis.scanned = [];\r\n\t\tthis.scannedFrozen = [];\r\n\t\tthis.scannedFrozenSchemas = [];\r\n\t\tthis.scannedFrozenValidationErrors = [];\r\n\t\tthis.validatedSchemasKey = 'tv4_validation_id';\r\n\t\tthis.validationErrorsKey = 'tv4_validation_errors_id';\r\n\t}\r\n\tif (trackUnknownProperties) {\r\n\t\tthis.trackUnknownProperties = true;\r\n\t\tthis.knownPropertyPaths = {};\r\n\t\tthis.unknownPropertyPaths = {};\r\n\t}\r\n\tthis.errorReporter = errorReporter || defaultErrorReporter('en');\r\n\tif (typeof this.errorReporter === 'string') {\r\n\t\tthrow new Error('debug');\r\n\t}\r\n\tthis.definedKeywords = {};\r\n\tif (parent) {\r\n\t\tfor (var key in parent.definedKeywords) {\r\n\t\t\tthis.definedKeywords[key] = parent.definedKeywords[key].slice(0);\r\n\t\t}\r\n\t}\r\n};\r\nValidatorContext.prototype.defineKeyword = function (keyword, keywordFunction) {\r\n\tthis.definedKeywords[keyword] = this.definedKeywords[keyword] || [];\r\n\tthis.definedKeywords[keyword].push(keywordFunction);\r\n};\r\nValidatorContext.prototype.createError = function (code, messageParams, dataPath, schemaPath, subErrors, data, schema) {\r\n\tvar error = new ValidationError(code, messageParams, dataPath, schemaPath, subErrors);\r\n\terror.message = this.errorReporter(error, data, schema);\r\n\treturn error;\r\n};\r\nValidatorContext.prototype.returnError = function (error) {\r\n\treturn error;\r\n};\r\nValidatorContext.prototype.collectError = function (error) {\r\n\tif (error) {\r\n\t\tthis.errors.push(error);\r\n\t}\r\n\treturn null;\r\n};\r\nValidatorContext.prototype.prefixErrors = function (startIndex, dataPath, schemaPath) {\r\n\tfor (var i = startIndex; i < this.errors.length; i++) {\r\n\t\tthis.errors[i] = this.errors[i].prefixWith(dataPath, schemaPath);\r\n\t}\r\n\treturn this;\r\n};\r\nValidatorContext.prototype.banUnknownProperties = function (data, schema) {\r\n\tfor (var unknownPath in this.unknownPropertyPaths) {\r\n\t\tvar error = this.createError(ErrorCodes.UNKNOWN_PROPERTY, {path: unknownPath}, unknownPath, \"\", null, data, schema);\r\n\t\tvar result = this.handleError(error);\r\n\t\tif (result) {\r\n\t\t\treturn result;\r\n\t\t}\r\n\t}\r\n\treturn null;\r\n};\r\n\r\nValidatorContext.prototype.addFormat = function (format, validator) {\r\n\tif (typeof format === 'object') {\r\n\t\tfor (var key in format) {\r\n\t\t\tthis.addFormat(key, format[key]);\r\n\t\t}\r\n\t\treturn this;\r\n\t}\r\n\tthis.formatValidators[format] = validator;\r\n};\r\nValidatorContext.prototype.resolveRefs = function (schema, urlHistory) {\r\n\tif (schema['$ref'] !== undefined) {\r\n\t\turlHistory = urlHistory || {};\r\n\t\tif (urlHistory[schema['$ref']]) {\r\n\t\t\treturn this.createError(ErrorCodes.CIRCULAR_REFERENCE, {urls: Object.keys(urlHistory).join(', ')}, '', '', null, undefined, schema);\r\n\t\t}\r\n\t\turlHistory[schema['$ref']] = true;\r\n\t\tschema = this.getSchema(schema['$ref'], urlHistory);\r\n\t}\r\n\treturn schema;\r\n};\r\nValidatorContext.prototype.getSchema = function (url, urlHistory) {\r\n\tvar schema;\r\n\tif (this.schemas[url] !== undefined) {\r\n\t\tschema = this.schemas[url];\r\n\t\treturn this.resolveRefs(schema, urlHistory);\r\n\t}\r\n\tvar baseUrl = url;\r\n\tvar fragment = \"\";\r\n\tif (url.indexOf('#') !== -1) {\r\n\t\tfragment = url.substring(url.indexOf(\"#\") + 1);\r\n\t\tbaseUrl = url.substring(0, url.indexOf(\"#\"));\r\n\t}\r\n\tif (typeof this.schemas[baseUrl] === 'object') {\r\n\t\tschema = this.schemas[baseUrl];\r\n\t\tvar pointerPath = decodeURIComponent(fragment);\r\n\t\tif (pointerPath === \"\") {\r\n\t\t\treturn this.resolveRefs(schema, urlHistory);\r\n\t\t} else if (pointerPath.charAt(0) !== \"/\") {\r\n\t\t\treturn undefined;\r\n\t\t}\r\n\t\tvar parts = pointerPath.split(\"/\").slice(1);\r\n\t\tfor (var i = 0; i < parts.length; i++) {\r\n\t\t\tvar component = parts[i].replace(/~1/g, \"/\").replace(/~0/g, \"~\");\r\n\t\t\tif (schema[component] === undefined) {\r\n\t\t\t\tschema = undefined;\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t\tschema = schema[component];\r\n\t\t}\r\n\t\tif (schema !== undefined) {\r\n\t\t\treturn this.resolveRefs(schema, urlHistory);\r\n\t\t}\r\n\t}\r\n\tif (this.missing[baseUrl] === undefined) {\r\n\t\tthis.missing.push(baseUrl);\r\n\t\tthis.missing[baseUrl] = baseUrl;\r\n\t\tthis.missingMap[baseUrl] = baseUrl;\r\n\t}\r\n};\r\nValidatorContext.prototype.searchSchemas = function (schema, url) {\r\n\tif (Array.isArray(schema)) {\r\n\t\tfor (var i = 0; i < schema.length; i++) {\r\n\t\t\tthis.searchSchemas(schema[i], url);\r\n\t\t}\r\n\t} else if (schema && typeof schema === \"object\") {\r\n\t\tif (typeof schema.id === \"string\") {\r\n\t\t\tif (isTrustedUrl(url, schema.id)) {\r\n\t\t\t\tif (this.schemas[schema.id] === undefined) {\r\n\t\t\t\t\tthis.schemas[schema.id] = schema;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\tfor (var key in schema) {\r\n\t\t\tif (key !== \"enum\") {\r\n\t\t\t\tif (typeof schema[key] === \"object\") {\r\n\t\t\t\t\tthis.searchSchemas(schema[key], url);\r\n\t\t\t\t} else if (key === \"$ref\") {\r\n\t\t\t\t\tvar uri = getDocumentUri(schema[key]);\r\n\t\t\t\t\tif (uri && this.schemas[uri] === undefined && this.missingMap[uri] === undefined) {\r\n\t\t\t\t\t\tthis.missingMap[uri] = uri;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\nValidatorContext.prototype.addSchema = function (url, schema) {\r\n\t//overload\r\n\tif (typeof url !== 'string' || typeof schema === 'undefined') {\r\n\t\tif (typeof url === 'object' && typeof url.id === 'string') {\r\n\t\t\tschema = url;\r\n\t\t\turl = schema.id;\r\n\t\t}\r\n\t\telse {\r\n\t\t\treturn;\r\n\t\t}\r\n\t}\r\n\tif (url === getDocumentUri(url) + \"#\") {\r\n\t\t// Remove empty fragment\r\n\t\turl = getDocumentUri(url);\r\n\t}\r\n\tthis.schemas[url] = schema;\r\n\tdelete this.missingMap[url];\r\n\tnormSchema(schema, url);\r\n\tthis.searchSchemas(schema, url);\r\n};\r\n\r\nValidatorContext.prototype.getSchemaMap = function () {\r\n\tvar map = {};\r\n\tfor (var key in this.schemas) {\r\n\t\tmap[key] = this.schemas[key];\r\n\t}\r\n\treturn map;\r\n};\r\n\r\nValidatorContext.prototype.getSchemaUris = function (filterRegExp) {\r\n\tvar list = [];\r\n\tfor (var key in this.schemas) {\r\n\t\tif (!filterRegExp || filterRegExp.test(key)) {\r\n\t\t\tlist.push(key);\r\n\t\t}\r\n\t}\r\n\treturn list;\r\n};\r\n\r\nValidatorContext.prototype.getMissingUris = function (filterRegExp) {\r\n\tvar list = [];\r\n\tfor (var key in this.missingMap) {\r\n\t\tif (!filterRegExp || filterRegExp.test(key)) {\r\n\t\t\tlist.push(key);\r\n\t\t}\r\n\t}\r\n\treturn list;\r\n};\r\n\r\nValidatorContext.prototype.dropSchemas = function () {\r\n\tthis.schemas = {};\r\n\tthis.reset();\r\n};\r\nValidatorContext.prototype.reset = function () {\r\n\tthis.missing = [];\r\n\tthis.missingMap = {};\r\n\tthis.errors = [];\r\n};\r\n\r\nValidatorContext.prototype.validateAll = function (data, schema, dataPathParts, schemaPathParts, dataPointerPath) {\r\n\tvar topLevel;\r\n\tschema = this.resolveRefs(schema);\r\n\tif (!schema) {\r\n\t\treturn null;\r\n\t} else if (schema instanceof ValidationError) {\r\n\t\tthis.errors.push(schema);\r\n\t\treturn schema;\r\n\t}\r\n\r\n\tvar startErrorCount = this.errors.length;\r\n\tvar frozenIndex, scannedFrozenSchemaIndex = null, scannedSchemasIndex = null;\r\n\tif (this.checkRecursive && data && typeof data === 'object') {\r\n\t\ttopLevel = !this.scanned.length;\r\n\t\tif (data[this.validatedSchemasKey]) {\r\n\t\t\tvar schemaIndex = data[this.validatedSchemasKey].indexOf(schema);\r\n\t\t\tif (schemaIndex !== -1) {\r\n\t\t\t\tthis.errors = this.errors.concat(data[this.validationErrorsKey][schemaIndex]);\r\n\t\t\t\treturn null;\r\n\t\t\t}\r\n\t\t}\r\n\t\tif (Object.isFrozen(data)) {\r\n\t\t\tfrozenIndex = this.scannedFrozen.indexOf(data);\r\n\t\t\tif (frozenIndex !== -1) {\r\n\t\t\t\tvar frozenSchemaIndex = this.scannedFrozenSchemas[frozenIndex].indexOf(schema);\r\n\t\t\t\tif (frozenSchemaIndex !== -1) {\r\n\t\t\t\t\tthis.errors = this.errors.concat(this.scannedFrozenValidationErrors[frozenIndex][frozenSchemaIndex]);\r\n\t\t\t\t\treturn null;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\tthis.scanned.push(data);\r\n\t\tif (Object.isFrozen(data)) {\r\n\t\t\tif (frozenIndex === -1) {\r\n\t\t\t\tfrozenIndex = this.scannedFrozen.length;\r\n\t\t\t\tthis.scannedFrozen.push(data);\r\n\t\t\t\tthis.scannedFrozenSchemas.push([]);\r\n\t\t\t}\r\n\t\t\tscannedFrozenSchemaIndex = this.scannedFrozenSchemas[frozenIndex].length;\r\n\t\t\tthis.scannedFrozenSchemas[frozenIndex][scannedFrozenSchemaIndex] = schema;\r\n\t\t\tthis.scannedFrozenValidationErrors[frozenIndex][scannedFrozenSchemaIndex] = [];\r\n\t\t} else {\r\n\t\t\tif (!data[this.validatedSchemasKey]) {\r\n\t\t\t\ttry {\r\n\t\t\t\t\tObject.defineProperty(data, this.validatedSchemasKey, {\r\n\t\t\t\t\t\tvalue: [],\r\n\t\t\t\t\t\tconfigurable: true\r\n\t\t\t\t\t});\r\n\t\t\t\t\tObject.defineProperty(data, this.validationErrorsKey, {\r\n\t\t\t\t\t\tvalue: [],\r\n\t\t\t\t\t\tconfigurable: true\r\n\t\t\t\t\t});\r\n\t\t\t\t} catch (e) {\r\n\t\t\t\t\t//IE 7/8 workaround\r\n\t\t\t\t\tdata[this.validatedSchemasKey] = [];\r\n\t\t\t\t\tdata[this.validationErrorsKey] = [];\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tscannedSchemasIndex = data[this.validatedSchemasKey].length;\r\n\t\t\tdata[this.validatedSchemasKey][scannedSchemasIndex] = schema;\r\n\t\t\tdata[this.validationErrorsKey][scannedSchemasIndex] = [];\r\n\t\t}\r\n\t}\r\n\r\n\tvar errorCount = this.errors.length;\r\n\tvar error = this.validateBasic(data, schema, dataPointerPath)\r\n\t\t|| this.validateNumeric(data, schema, dataPointerPath)\r\n\t\t|| this.validateString(data, schema, dataPointerPath)\r\n\t\t|| this.validateArray(data, schema, dataPointerPath)\r\n\t\t|| this.validateObject(data, schema, dataPointerPath)\r\n\t\t|| this.validateCombinations(data, schema, dataPointerPath)\r\n\t\t|| this.validateHypermedia(data, schema, dataPointerPath)\r\n\t\t|| this.validateFormat(data, schema, dataPointerPath)\r\n\t\t|| this.validateDefinedKeywords(data, schema, dataPointerPath)\r\n\t\t|| null;\r\n\r\n\tif (topLevel) {\r\n\t\twhile (this.scanned.length) {\r\n\t\t\tvar item = this.scanned.pop();\r\n\t\t\tdelete item[this.validatedSchemasKey];\r\n\t\t}\r\n\t\tthis.scannedFrozen = [];\r\n\t\tthis.scannedFrozenSchemas = [];\r\n\t}\r\n\r\n\tif (error || errorCount !== this.errors.length) {\r\n\t\twhile ((dataPathParts && dataPathParts.length) || (schemaPathParts && schemaPathParts.length)) {\r\n\t\t\tvar dataPart = (dataPathParts && dataPathParts.length) ? \"\" + dataPathParts.pop() : null;\r\n\t\t\tvar schemaPart = (schemaPathParts && schemaPathParts.length) ? \"\" + schemaPathParts.pop() : null;\r\n\t\t\tif (error) {\r\n\t\t\t\terror = error.prefixWith(dataPart, schemaPart);\r\n\t\t\t}\r\n\t\t\tthis.prefixErrors(errorCount, dataPart, schemaPart);\r\n\t\t}\r\n\t}\r\n\r\n\tif (scannedFrozenSchemaIndex !== null) {\r\n\t\tthis.scannedFrozenValidationErrors[frozenIndex][scannedFrozenSchemaIndex] = this.errors.slice(startErrorCount);\r\n\t} else if (scannedSchemasIndex !== null) {\r\n\t\tdata[this.validationErrorsKey][scannedSchemasIndex] = this.errors.slice(startErrorCount);\r\n\t}\r\n\r\n\treturn this.handleError(error);\r\n};\r\nValidatorContext.prototype.validateFormat = function (data, schema) {\r\n\tif (typeof schema.format !== 'string' || !this.formatValidators[schema.format]) {\r\n\t\treturn null;\r\n\t}\r\n\tvar errorMessage = this.formatValidators[schema.format].call(null, data, schema);\r\n\tif (typeof errorMessage === 'string' || typeof errorMessage === 'number') {\r\n\t\treturn this.createError(ErrorCodes.FORMAT_CUSTOM, {message: errorMessage}, '', '/format', null, data, schema);\r\n\t} else if (errorMessage && typeof errorMessage === 'object') {\r\n\t\treturn this.createError(ErrorCodes.FORMAT_CUSTOM, {message: errorMessage.message || \"?\"}, errorMessage.dataPath || '', errorMessage.schemaPath || \"/format\", null, data, schema);\r\n\t}\r\n\treturn null;\r\n};\r\nValidatorContext.prototype.validateDefinedKeywords = function (data, schema, dataPointerPath) {\r\n\tfor (var key in this.definedKeywords) {\r\n\t\tif (typeof schema[key] === 'undefined') {\r\n\t\t\tcontinue;\r\n\t\t}\r\n\t\tvar validationFunctions = this.definedKeywords[key];\r\n\t\tfor (var i = 0; i < validationFunctions.length; i++) {\r\n\t\t\tvar func = validationFunctions[i];\r\n\t\t\tvar result = func(data, schema[key], schema, dataPointerPath);\r\n\t\t\tif (typeof result === 'string' || typeof result === 'number') {\r\n\t\t\t\treturn this.createError(ErrorCodes.KEYWORD_CUSTOM, {key: key, message: result}, '', '', null, data, schema).prefixWith(null, key);\r\n\t\t\t} else if (result && typeof result === 'object') {\r\n\t\t\t\tvar code = result.code;\r\n\t\t\t\tif (typeof code === 'string') {\r\n\t\t\t\t\tif (!ErrorCodes[code]) {\r\n\t\t\t\t\t\tthrow new Error('Undefined error code (use defineError): ' + code);\r\n\t\t\t\t\t}\r\n\t\t\t\t\tcode = ErrorCodes[code];\r\n\t\t\t\t} else if (typeof code !== 'number') {\r\n\t\t\t\t\tcode = ErrorCodes.KEYWORD_CUSTOM;\r\n\t\t\t\t}\r\n\t\t\t\tvar messageParams = (typeof result.message === 'object') ? result.message : {key: key, message: result.message || \"?\"};\r\n\t\t\t\tvar schemaPath = result.schemaPath || (\"/\" + key.replace(/~/g, '~0').replace(/\\//g, '~1'));\r\n\t\t\t\treturn this.createError(code, messageParams, result.dataPath || null, schemaPath, null, data, schema);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\treturn null;\r\n};\r\n\r\nfunction recursiveCompare(A, B) {\r\n\tif (A === B) {\r\n\t\treturn true;\r\n\t}\r\n\tif (A && B && typeof A === \"object\" && typeof B === \"object\") {\r\n\t\tif (Array.isArray(A) !== Array.isArray(B)) {\r\n\t\t\treturn false;\r\n\t\t} else if (Array.isArray(A)) {\r\n\t\t\tif (A.length !== B.length) {\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t\tfor (var i = 0; i < A.length; i++) {\r\n\t\t\t\tif (!recursiveCompare(A[i], B[i])) {\r\n\t\t\t\t\treturn false;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tvar key;\r\n\t\t\tfor (key in A) {\r\n\t\t\t\tif (B[key] === undefined && A[key] !== undefined) {\r\n\t\t\t\t\treturn false;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tfor (key in B) {\r\n\t\t\t\tif (A[key] === undefined && B[key] !== undefined) {\r\n\t\t\t\t\treturn false;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tfor (key in A) {\r\n\t\t\t\tif (!recursiveCompare(A[key], B[key])) {\r\n\t\t\t\t\treturn false;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn true;\r\n\t}\r\n\treturn false;\r\n}\r\n\r\nValidatorContext.prototype.validateBasic = function validateBasic(data, schema, dataPointerPath) {\r\n\tvar error;\r\n\tif (error = this.validateType(data, schema, dataPointerPath)) {\r\n\t\treturn error.prefixWith(null, \"type\");\r\n\t}\r\n\tif (error = this.validateEnum(data, schema, dataPointerPath)) {\r\n\t\treturn error.prefixWith(null, \"type\");\r\n\t}\r\n\treturn null;\r\n};\r\n\r\nValidatorContext.prototype.validateType = function validateType(data, schema) {\r\n\tif (schema.type === undefined) {\r\n\t\treturn null;\r\n\t}\r\n\tvar dataType = typeof data;\r\n\tif (data === null) {\r\n\t\tdataType = \"null\";\r\n\t} else if (Array.isArray(data)) {\r\n\t\tdataType = \"array\";\r\n\t}\r\n\tvar allowedTypes = schema.type;\r\n\tif (!Array.isArray(allowedTypes)) {\r\n\t\tallowedTypes = [allowedTypes];\r\n\t}\r\n\r\n\tfor (var i = 0; i < allowedTypes.length; i++) {\r\n\t\tvar type = allowedTypes[i];\r\n\t\tif (type === dataType || (type === \"integer\" && dataType === \"number\" && (data % 1 === 0))) {\r\n\t\t\treturn null;\r\n\t\t}\r\n\t}\r\n\treturn this.createError(ErrorCodes.INVALID_TYPE, {type: dataType, expected: allowedTypes.join(\"/\")}, '', '', null, data, schema);\r\n};\r\n\r\nValidatorContext.prototype.validateEnum = function validateEnum(data, schema) {\r\n\tif (schema[\"enum\"] === undefined) {\r\n\t\treturn null;\r\n\t}\r\n\tfor (var i = 0; i < schema[\"enum\"].length; i++) {\r\n\t\tvar enumVal = schema[\"enum\"][i];\r\n\t\tif (recursiveCompare(data, enumVal)) {\r\n\t\t\treturn null;\r\n\t\t}\r\n\t}\r\n\treturn this.createError(ErrorCodes.ENUM_MISMATCH, {value: (typeof JSON !== 'undefined') ? JSON.stringify(data) : data}, '', '', null, data, schema);\r\n};\r\n\r\nValidatorContext.prototype.validateNumeric = function validateNumeric(data, schema, dataPointerPath) {\r\n\treturn this.validateMultipleOf(data, schema, dataPointerPath)\r\n\t\t|| this.validateMinMax(data, schema, dataPointerPath)\r\n\t\t|| this.validateNaN(data, schema, dataPointerPath)\r\n\t\t|| null;\r\n};\r\n\r\nvar CLOSE_ENOUGH_LOW = Math.pow(2, -51);\r\nvar CLOSE_ENOUGH_HIGH = 1 - CLOSE_ENOUGH_LOW;\r\nValidatorContext.prototype.validateMultipleOf = function validateMultipleOf(data, schema) {\r\n\tvar multipleOf = schema.multipleOf || schema.divisibleBy;\r\n\tif (multipleOf === undefined) {\r\n\t\treturn null;\r\n\t}\r\n\tif (typeof data === \"number\") {\r\n\t\tvar remainder = (data/multipleOf)%1;\r\n\t\tif (remainder >= CLOSE_ENOUGH_LOW && remainder < CLOSE_ENOUGH_HIGH) {\r\n\t\t\treturn this.createError(ErrorCodes.NUMBER_MULTIPLE_OF, {value: data, multipleOf: multipleOf}, '', '', null, data, schema);\r\n\t\t}\r\n\t}\r\n\treturn null;\r\n};\r\n\r\nValidatorContext.prototype.validateMinMax = function validateMinMax(data, schema) {\r\n\tif (typeof data !== \"number\") {\r\n\t\treturn null;\r\n\t}\r\n\tif (schema.minimum !== undefined) {\r\n\t\tif (data < schema.minimum) {\r\n\t\t\treturn this.createError(ErrorCodes.NUMBER_MINIMUM, {value: data, minimum: schema.minimum}, '', '/minimum', null, data, schema);\r\n\t\t}\r\n\t\tif (schema.exclusiveMinimum && data === schema.minimum) {\r\n\t\t\treturn this.createError(ErrorCodes.NUMBER_MINIMUM_EXCLUSIVE, {value: data, minimum: schema.minimum}, '', '/exclusiveMinimum', null, data, schema);\r\n\t\t}\r\n\t}\r\n\tif (schema.maximum !== undefined) {\r\n\t\tif (data > schema.maximum) {\r\n\t\t\treturn this.createError(ErrorCodes.NUMBER_MAXIMUM, {value: data, maximum: schema.maximum}, '', '/maximum', null, data, schema);\r\n\t\t}\r\n\t\tif (schema.exclusiveMaximum && data === schema.maximum) {\r\n\t\t\treturn this.createError(ErrorCodes.NUMBER_MAXIMUM_EXCLUSIVE, {value: data, maximum: schema.maximum}, '', '/exclusiveMaximum', null, data, schema);\r\n\t\t}\r\n\t}\r\n\treturn null;\r\n};\r\n\r\nValidatorContext.prototype.validateNaN = function validateNaN(data, schema) {\r\n\tif (typeof data !== \"number\") {\r\n\t\treturn null;\r\n\t}\r\n\tif (isNaN(data) === true || data === Infinity || data === -Infinity) {\r\n\t\treturn this.createError(ErrorCodes.NUMBER_NOT_A_NUMBER, {value: data}, '', '/type', null, data, schema);\r\n\t}\r\n\treturn null;\r\n};\r\n\r\nValidatorContext.prototype.validateString = function validateString(data, schema, dataPointerPath) {\r\n\treturn this.validateStringLength(data, schema, dataPointerPath)\r\n\t\t|| this.validateStringPattern(data, schema, dataPointerPath)\r\n\t\t|| null;\r\n};\r\n\r\nValidatorContext.prototype.validateStringLength = function validateStringLength(data, schema) {\r\n\tif (typeof data !== \"string\") {\r\n\t\treturn null;\r\n\t}\r\n\tif (schema.minLength !== undefined) {\r\n\t\tif (data.length < schema.minLength) {\r\n\t\t\treturn this.createError(ErrorCodes.STRING_LENGTH_SHORT, {length: data.length, minimum: schema.minLength}, '', '/minLength', null, data, schema);\r\n\t\t}\r\n\t}\r\n\tif (schema.maxLength !== undefined) {\r\n\t\tif (data.length > schema.maxLength) {\r\n\t\t\treturn this.createError(ErrorCodes.STRING_LENGTH_LONG, {length: data.length, maximum: schema.maxLength}, '', '/maxLength', null, data, schema);\r\n\t\t}\r\n\t}\r\n\treturn null;\r\n};\r\n\r\nValidatorContext.prototype.validateStringPattern = function validateStringPattern(data, schema) {\r\n\tif (typeof data !== \"string\" || (typeof schema.pattern !== \"string\" && !(schema.pattern instanceof RegExp))) {\r\n\t\treturn null;\r\n\t}\r\n\tvar regexp;\r\n\tif (schema.pattern instanceof RegExp) {\r\n\t regexp = schema.pattern;\r\n\t}\r\n\telse {\r\n\t var body, flags = '';\r\n\t // Check for regular expression literals\r\n\t // @see http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.5\r\n\t var literal = schema.pattern.match(/^\\/(.+)\\/([img]*)$/);\r\n\t if (literal) {\r\n\t body = literal[1];\r\n\t flags = literal[2];\r\n\t }\r\n\t else {\r\n\t body = schema.pattern;\r\n\t }\r\n\t regexp = new RegExp(body, flags);\r\n\t}\r\n\tif (!regexp.test(data)) {\r\n\t\treturn this.createError(ErrorCodes.STRING_PATTERN, {pattern: schema.pattern}, '', '/pattern', null, data, schema);\r\n\t}\r\n\treturn null;\r\n};\r\n\r\nValidatorContext.prototype.validateArray = function validateArray(data, schema, dataPointerPath) {\r\n\tif (!Array.isArray(data)) {\r\n\t\treturn null;\r\n\t}\r\n\treturn this.validateArrayLength(data, schema, dataPointerPath)\r\n\t\t|| this.validateArrayUniqueItems(data, schema, dataPointerPath)\r\n\t\t|| this.validateArrayItems(data, schema, dataPointerPath)\r\n\t\t|| null;\r\n};\r\n\r\nValidatorContext.prototype.validateArrayLength = function validateArrayLength(data, schema) {\r\n\tvar error;\r\n\tif (schema.minItems !== undefined) {\r\n\t\tif (data.length < schema.minItems) {\r\n\t\t\terror = this.createError(ErrorCodes.ARRAY_LENGTH_SHORT, {length: data.length, minimum: schema.minItems}, '', '/minItems', null, data, schema);\r\n\t\t\tif (this.handleError(error)) {\r\n\t\t\t\treturn error;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\tif (schema.maxItems !== undefined) {\r\n\t\tif (data.length > schema.maxItems) {\r\n\t\t\terror = this.createError(ErrorCodes.ARRAY_LENGTH_LONG, {length: data.length, maximum: schema.maxItems}, '', '/maxItems', null, data, schema);\r\n\t\t\tif (this.handleError(error)) {\r\n\t\t\t\treturn error;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\treturn null;\r\n};\r\n\r\nValidatorContext.prototype.validateArrayUniqueItems = function validateArrayUniqueItems(data, schema) {\r\n\tif (schema.uniqueItems) {\r\n\t\tfor (var i = 0; i < data.length; i++) {\r\n\t\t\tfor (var j = i + 1; j < data.length; j++) {\r\n\t\t\t\tif (recursiveCompare(data[i], data[j])) {\r\n\t\t\t\t\tvar error = this.createError(ErrorCodes.ARRAY_UNIQUE, {match1: i, match2: j}, '', '/uniqueItems', null, data, schema);\r\n\t\t\t\t\tif (this.handleError(error)) {\r\n\t\t\t\t\t\treturn error;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\treturn null;\r\n};\r\n\r\nValidatorContext.prototype.validateArrayItems = function validateArrayItems(data, schema, dataPointerPath) {\r\n\tif (schema.items === undefined) {\r\n\t\treturn null;\r\n\t}\r\n\tvar error, i;\r\n\tif (Array.isArray(schema.items)) {\r\n\t\tfor (i = 0; i < data.length; i++) {\r\n\t\t\tif (i < schema.items.length) {\r\n\t\t\t\tif (error = this.validateAll(data[i], schema.items[i], [i], [\"items\", i], dataPointerPath + \"/\" + i)) {\r\n\t\t\t\t\treturn error;\r\n\t\t\t\t}\r\n\t\t\t} else if (schema.additionalItems !== undefined) {\r\n\t\t\t\tif (typeof schema.additionalItems === \"boolean\") {\r\n\t\t\t\t\tif (!schema.additionalItems) {\r\n\t\t\t\t\t\terror = (this.createError(ErrorCodes.ARRAY_ADDITIONAL_ITEMS, {}, '/' + i, '/additionalItems', null, data, schema));\r\n\t\t\t\t\t\tif (this.handleError(error)) {\r\n\t\t\t\t\t\t\treturn error;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t} else if (error = this.validateAll(data[i], schema.additionalItems, [i], [\"additionalItems\"], dataPointerPath + \"/\" + i)) {\r\n\t\t\t\t\treturn error;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t} else {\r\n\t\tfor (i = 0; i < data.length; i++) {\r\n\t\t\tif (error = this.validateAll(data[i], schema.items, [i], [\"items\"], dataPointerPath + \"/\" + i)) {\r\n\t\t\t\treturn error;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\treturn null;\r\n};\r\n\r\nValidatorContext.prototype.validateObject = function validateObject(data, schema, dataPointerPath) {\r\n\tif (typeof data !== \"object\" || data === null || Array.isArray(data)) {\r\n\t\treturn null;\r\n\t}\r\n\treturn this.validateObjectMinMaxProperties(data, schema, dataPointerPath)\r\n\t\t|| this.validateObjectRequiredProperties(data, schema, dataPointerPath)\r\n\t\t|| this.validateObjectProperties(data, schema, dataPointerPath)\r\n\t\t|| this.validateObjectDependencies(data, schema, dataPointerPath)\r\n\t\t|| null;\r\n};\r\n\r\nValidatorContext.prototype.validateObjectMinMaxProperties = function validateObjectMinMaxProperties(data, schema) {\r\n\tvar keys = Object.keys(data);\r\n\tvar error;\r\n\tif (schema.minProperties !== undefined) {\r\n\t\tif (keys.length < schema.minProperties) {\r\n\t\t\terror = this.createError(ErrorCodes.OBJECT_PROPERTIES_MINIMUM, {propertyCount: keys.length, minimum: schema.minProperties}, '', '/minProperties', null, data, schema);\r\n\t\t\tif (this.handleError(error)) {\r\n\t\t\t\treturn error;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\tif (schema.maxProperties !== undefined) {\r\n\t\tif (keys.length > schema.maxProperties) {\r\n\t\t\terror = this.createError(ErrorCodes.OBJECT_PROPERTIES_MAXIMUM, {propertyCount: keys.length, maximum: schema.maxProperties}, '', '/maxProperties', null, data, schema);\r\n\t\t\tif (this.handleError(error)) {\r\n\t\t\t\treturn error;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\treturn null;\r\n};\r\n\r\nValidatorContext.prototype.validateObjectRequiredProperties = function validateObjectRequiredProperties(data, schema) {\r\n\tif (schema.required !== undefined) {\r\n\t\tfor (var i = 0; i < schema.required.length; i++) {\r\n\t\t\tvar key = schema.required[i];\r\n\t\t\tif (data[key] === undefined) {\r\n\t\t\t\tvar error = this.createError(ErrorCodes.OBJECT_REQUIRED, {key: key}, '', '/required/' + i, null, data, schema);\r\n\t\t\t\tif (this.handleError(error)) {\r\n\t\t\t\t\treturn error;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\treturn null;\r\n};\r\n\r\nValidatorContext.prototype.validateObjectProperties = function validateObjectProperties(data, schema, dataPointerPath) {\r\n\tvar error;\r\n\tfor (var key in data) {\r\n\t\tvar keyPointerPath = dataPointerPath + \"/\" + key.replace(/~/g, '~0').replace(/\\//g, '~1');\r\n\t\tvar foundMatch = false;\r\n\t\tif (schema.properties !== undefined && schema.properties[key] !== undefined) {\r\n\t\t\tfoundMatch = true;\r\n\t\t\tif (error = this.validateAll(data[key], schema.properties[key], [key], [\"properties\", key], keyPointerPath)) {\r\n\t\t\t\treturn error;\r\n\t\t\t}\r\n\t\t}\r\n\t\tif (schema.patternProperties !== undefined) {\r\n\t\t\tfor (var patternKey in schema.patternProperties) {\r\n\t\t\t\tvar regexp = new RegExp(patternKey);\r\n\t\t\t\tif (regexp.test(key)) {\r\n\t\t\t\t\tfoundMatch = true;\r\n\t\t\t\t\tif (error = this.validateAll(data[key], schema.patternProperties[patternKey], [key], [\"patternProperties\", patternKey], keyPointerPath)) {\r\n\t\t\t\t\t\treturn error;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\tif (!foundMatch) {\r\n\t\t\tif (schema.additionalProperties !== undefined) {\r\n\t\t\t\tif (this.trackUnknownProperties) {\r\n\t\t\t\t\tthis.knownPropertyPaths[keyPointerPath] = true;\r\n\t\t\t\t\tdelete this.unknownPropertyPaths[keyPointerPath];\r\n\t\t\t\t}\r\n\t\t\t\tif (typeof schema.additionalProperties === \"boolean\") {\r\n\t\t\t\t\tif (!schema.additionalProperties) {\r\n\t\t\t\t\t\terror = this.createError(ErrorCodes.OBJECT_ADDITIONAL_PROPERTIES, {key: key}, '', '/additionalProperties', null, data, schema).prefixWith(key, null);\r\n\t\t\t\t\t\tif (this.handleError(error)) {\r\n\t\t\t\t\t\t\treturn error;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t} else {\r\n\t\t\t\t\tif (error = this.validateAll(data[key], schema.additionalProperties, [key], [\"additionalProperties\"], keyPointerPath)) {\r\n\t\t\t\t\t\treturn error;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t} else if (this.trackUnknownProperties && !this.knownPropertyPaths[keyPointerPath]) {\r\n\t\t\t\tthis.unknownPropertyPaths[keyPointerPath] = true;\r\n\t\t\t}\r\n\t\t} else if (this.trackUnknownProperties) {\r\n\t\t\tthis.knownPropertyPaths[keyPointerPath] = true;\r\n\t\t\tdelete this.unknownPropertyPaths[keyPointerPath];\r\n\t\t}\r\n\t}\r\n\treturn null;\r\n};\r\n\r\nValidatorContext.prototype.validateObjectDependencies = function validateObjectDependencies(data, schema, dataPointerPath) {\r\n\tvar error;\r\n\tif (schema.dependencies !== undefined) {\r\n\t\tfor (var depKey in schema.dependencies) {\r\n\t\t\tif (data[depKey] !== undefined) {\r\n\t\t\t\tvar dep = schema.dependencies[depKey];\r\n\t\t\t\tif (typeof dep === \"string\") {\r\n\t\t\t\t\tif (data[dep] === undefined) {\r\n\t\t\t\t\t\terror = this.createError(ErrorCodes.OBJECT_DEPENDENCY_KEY, {key: depKey, missing: dep}, '', '', null, data, schema).prefixWith(null, depKey).prefixWith(null, \"dependencies\");\r\n\t\t\t\t\t\tif (this.handleError(error)) {\r\n\t\t\t\t\t\t\treturn error;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t} else if (Array.isArray(dep)) {\r\n\t\t\t\t\tfor (var i = 0; i < dep.length; i++) {\r\n\t\t\t\t\t\tvar requiredKey = dep[i];\r\n\t\t\t\t\t\tif (data[requiredKey] === undefined) {\r\n\t\t\t\t\t\t\terror = this.createError(ErrorCodes.OBJECT_DEPENDENCY_KEY, {key: depKey, missing: requiredKey}, '', '/' + i, null, data, schema).prefixWith(null, depKey).prefixWith(null, \"dependencies\");\r\n\t\t\t\t\t\t\tif (this.handleError(error)) {\r\n\t\t\t\t\t\t\t\treturn error;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t} else {\r\n\t\t\t\t\tif (error = this.validateAll(data, dep, [], [\"dependencies\", depKey], dataPointerPath)) {\r\n\t\t\t\t\t\treturn error;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\treturn null;\r\n};\r\n\r\nValidatorContext.prototype.validateCombinations = function validateCombinations(data, schema, dataPointerPath) {\r\n\treturn this.validateAllOf(data, schema, dataPointerPath)\r\n\t\t|| this.validateAnyOf(data, schema, dataPointerPath)\r\n\t\t|| this.validateOneOf(data, schema, dataPointerPath)\r\n\t\t|| this.validateNot(data, schema, dataPointerPath)\r\n\t\t|| null;\r\n};\r\n\r\nValidatorContext.prototype.validateAllOf = function validateAllOf(data, schema, dataPointerPath) {\r\n\tif (schema.allOf === undefined) {\r\n\t\treturn null;\r\n\t}\r\n\tvar error;\r\n\tfor (var i = 0; i < schema.allOf.length; i++) {\r\n\t\tvar subSchema = schema.allOf[i];\r\n\t\tif (error = this.validateAll(data, subSchema, [], [\"allOf\", i], dataPointerPath)) {\r\n\t\t\treturn error;\r\n\t\t}\r\n\t}\r\n\treturn null;\r\n};\r\n\r\nValidatorContext.prototype.validateAnyOf = function validateAnyOf(data, schema, dataPointerPath) {\r\n\tif (schema.anyOf === undefined) {\r\n\t\treturn null;\r\n\t}\r\n\tvar errors = [];\r\n\tvar startErrorCount = this.errors.length;\r\n\tvar oldUnknownPropertyPaths, oldKnownPropertyPaths;\r\n\tif (this.trackUnknownProperties) {\r\n\t\toldUnknownPropertyPaths = this.unknownPropertyPaths;\r\n\t\toldKnownPropertyPaths = this.knownPropertyPaths;\r\n\t}\r\n\tvar errorAtEnd = true;\r\n\tfor (var i = 0; i < schema.anyOf.length; i++) {\r\n\t\tif (this.trackUnknownProperties) {\r\n\t\t\tthis.unknownPropertyPaths = {};\r\n\t\t\tthis.knownPropertyPaths = {};\r\n\t\t}\r\n\t\tvar subSchema = schema.anyOf[i];\r\n\r\n\t\tvar errorCount = this.errors.length;\r\n\t\tvar error = this.validateAll(data, subSchema, [], [\"anyOf\", i], dataPointerPath);\r\n\r\n\t\tif (error === null && errorCount === this.errors.length) {\r\n\t\t\tthis.errors = this.errors.slice(0, startErrorCount);\r\n\r\n\t\t\tif (this.trackUnknownProperties) {\r\n\t\t\t\tfor (var knownKey in this.knownPropertyPaths) {\r\n\t\t\t\t\toldKnownPropertyPaths[knownKey] = true;\r\n\t\t\t\t\tdelete oldUnknownPropertyPaths[knownKey];\r\n\t\t\t\t}\r\n\t\t\t\tfor (var unknownKey in this.unknownPropertyPaths) {\r\n\t\t\t\t\tif (!oldKnownPropertyPaths[unknownKey]) {\r\n\t\t\t\t\t\toldUnknownPropertyPaths[unknownKey] = true;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t// We need to continue looping so we catch all the property definitions, but we don't want to return an error\r\n\t\t\t\terrorAtEnd = false;\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\r\n\t\t\treturn null;\r\n\t\t}\r\n\t\tif (error) {\r\n\t\t\terrors.push(error.prefixWith(null, \"\" + i).prefixWith(null, \"anyOf\"));\r\n\t\t}\r\n\t}\r\n\tif (this.trackUnknownProperties) {\r\n\t\tthis.unknownPropertyPaths = oldUnknownPropertyPaths;\r\n\t\tthis.knownPropertyPaths = oldKnownPropertyPaths;\r\n\t}\r\n\tif (errorAtEnd) {\r\n\t\terrors = errors.concat(this.errors.slice(startErrorCount));\r\n\t\tthis.errors = this.errors.slice(0, startErrorCount);\r\n\t\treturn this.createError(ErrorCodes.ANY_OF_MISSING, {}, \"\", \"/anyOf\", errors, data, schema);\r\n\t}\r\n};\r\n\r\nValidatorContext.prototype.validateOneOf = function validateOneOf(data, schema, dataPointerPath) {\r\n\tif (schema.oneOf === undefined) {\r\n\t\treturn null;\r\n\t}\r\n\tvar validIndex = null;\r\n\tvar errors = [];\r\n\tvar startErrorCount = this.errors.length;\r\n\tvar oldUnknownPropertyPaths, oldKnownPropertyPaths;\r\n\tif (this.trackUnknownProperties) {\r\n\t\toldUnknownPropertyPaths = this.unknownPropertyPaths;\r\n\t\toldKnownPropertyPaths = this.knownPropertyPaths;\r\n\t}\r\n\tfor (var i = 0; i < schema.oneOf.length; i++) {\r\n\t\tif (this.trackUnknownProperties) {\r\n\t\t\tthis.unknownPropertyPaths = {};\r\n\t\t\tthis.knownPropertyPaths = {};\r\n\t\t}\r\n\t\tvar subSchema = schema.oneOf[i];\r\n\r\n\t\tvar errorCount = this.errors.length;\r\n\t\tvar error = this.validateAll(data, subSchema, [], [\"oneOf\", i], dataPointerPath);\r\n\r\n\t\tif (error === null && errorCount === this.errors.length) {\r\n\t\t\tif (validIndex === null) {\r\n\t\t\t\tvalidIndex = i;\r\n\t\t\t} else {\r\n\t\t\t\tthis.errors = this.errors.slice(0, startErrorCount);\r\n\t\t\t\treturn this.createError(ErrorCodes.ONE_OF_MULTIPLE, {index1: validIndex, index2: i}, \"\", \"/oneOf\", null, data, schema);\r\n\t\t\t}\r\n\t\t\tif (this.trackUnknownProperties) {\r\n\t\t\t\tfor (var knownKey in this.knownPropertyPaths) {\r\n\t\t\t\t\toldKnownPropertyPaths[knownKey] = true;\r\n\t\t\t\t\tdelete oldUnknownPropertyPaths[knownKey];\r\n\t\t\t\t}\r\n\t\t\t\tfor (var unknownKey in this.unknownPropertyPaths) {\r\n\t\t\t\t\tif (!oldKnownPropertyPaths[unknownKey]) {\r\n\t\t\t\t\t\toldUnknownPropertyPaths[unknownKey] = true;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t} else if (error) {\r\n\t\t\terrors.push(error);\r\n\t\t}\r\n\t}\r\n\tif (this.trackUnknownProperties) {\r\n\t\tthis.unknownPropertyPaths = oldUnknownPropertyPaths;\r\n\t\tthis.knownPropertyPaths = oldKnownPropertyPaths;\r\n\t}\r\n\tif (validIndex === null) {\r\n\t\terrors = errors.concat(this.errors.slice(startErrorCount));\r\n\t\tthis.errors = this.errors.slice(0, startErrorCount);\r\n\t\treturn this.createError(ErrorCodes.ONE_OF_MISSING, {}, \"\", \"/oneOf\", errors, data, schema);\r\n\t} else {\r\n\t\tthis.errors = this.errors.slice(0, startErrorCount);\r\n\t}\r\n\treturn null;\r\n};\r\n\r\nValidatorContext.prototype.validateNot = function validateNot(data, schema, dataPointerPath) {\r\n\tif (schema.not === undefined) {\r\n\t\treturn null;\r\n\t}\r\n\tvar oldErrorCount = this.errors.length;\r\n\tvar oldUnknownPropertyPaths, oldKnownPropertyPaths;\r\n\tif (this.trackUnknownProperties) {\r\n\t\toldUnknownPropertyPaths = this.unknownPropertyPaths;\r\n\t\toldKnownPropertyPaths = this.knownPropertyPaths;\r\n\t\tthis.unknownPropertyPaths = {};\r\n\t\tthis.knownPropertyPaths = {};\r\n\t}\r\n\tvar error = this.validateAll(data, schema.not, null, null, dataPointerPath);\r\n\tvar notErrors = this.errors.slice(oldErrorCount);\r\n\tthis.errors = this.errors.slice(0, oldErrorCount);\r\n\tif (this.trackUnknownProperties) {\r\n\t\tthis.unknownPropertyPaths = oldUnknownPropertyPaths;\r\n\t\tthis.knownPropertyPaths = oldKnownPropertyPaths;\r\n\t}\r\n\tif (error === null && notErrors.length === 0) {\r\n\t\treturn this.createError(ErrorCodes.NOT_PASSED, {}, \"\", \"/not\", null, data, schema);\r\n\t}\r\n\treturn null;\r\n};\r\n\r\nValidatorContext.prototype.validateHypermedia = function validateCombinations(data, schema, dataPointerPath) {\r\n\tif (!schema.links) {\r\n\t\treturn null;\r\n\t}\r\n\tvar error;\r\n\tfor (var i = 0; i < schema.links.length; i++) {\r\n\t\tvar ldo = schema.links[i];\r\n\t\tif (ldo.rel === \"describedby\") {\r\n\t\t\tvar template = new UriTemplate(ldo.href);\r\n\t\t\tvar allPresent = true;\r\n\t\t\tfor (var j = 0; j < template.varNames.length; j++) {\r\n\t\t\t\tif (!(template.varNames[j] in data)) {\r\n\t\t\t\t\tallPresent = false;\r\n\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tif (allPresent) {\r\n\t\t\t\tvar schemaUrl = template.fillFromObject(data);\r\n\t\t\t\tvar subSchema = {\"$ref\": schemaUrl};\r\n\t\t\t\tif (error = this.validateAll(data, subSchema, [], [\"links\", i], dataPointerPath)) {\r\n\t\t\t\t\treturn error;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n\r\n// parseURI() and resolveUrl() are from https://gist.github.com/1088850\r\n// - released as public domain by author (\"Yaffle\") - see comments on gist\r\n\r\nfunction parseURI(url) {\r\n\tvar m = String(url).replace(/^\\s+|\\s+$/g, '').match(/^([^:\\/?#]+:)?(\\/\\/(?:[^:@]*(?::[^:@]*)?@)?(([^:\\/?#]*)(?::(\\d*))?))?([^?#]*)(\\?[^#]*)?(#[\\s\\S]*)?/);\r\n\t// authority = '//' + user + ':' + pass '@' + hostname + ':' port\r\n\treturn (m ? {\r\n\t\thref : m[0] || '',\r\n\t\tprotocol : m[1] || '',\r\n\t\tauthority: m[2] || '',\r\n\t\thost : m[3] || '',\r\n\t\thostname : m[4] || '',\r\n\t\tport : m[5] || '',\r\n\t\tpathname : m[6] || '',\r\n\t\tsearch : m[7] || '',\r\n\t\thash : m[8] || ''\r\n\t} : null);\r\n}\r\n\r\nfunction resolveUrl(base, href) {// RFC 3986\r\n\r\n\tfunction removeDotSegments(input) {\r\n\t\tvar output = [];\r\n\t\tinput.replace(/^(\\.\\.?(\\/|$))+/, '')\r\n\t\t\t.replace(/\\/(\\.(\\/|$))+/g, '/')\r\n\t\t\t.replace(/\\/\\.\\.$/, '/../')\r\n\t\t\t.replace(/\\/?[^\\/]*/g, function (p) {\r\n\t\t\t\tif (p === '/..') {\r\n\t\t\t\t\toutput.pop();\r\n\t\t\t\t} else {\r\n\t\t\t\t\toutput.push(p);\r\n\t\t\t\t}\r\n\t\t});\r\n\t\treturn output.join('').replace(/^\\//, input.charAt(0) === '/' ? '/' : '');\r\n\t}\r\n\r\n\thref = parseURI(href || '');\r\n\tbase = parseURI(base || '');\r\n\r\n\treturn !href || !base ? null : (href.protocol || base.protocol) +\r\n\t\t(href.protocol || href.authority ? href.authority : base.authority) +\r\n\t\tremoveDotSegments(href.protocol || href.authority || href.pathname.charAt(0) === '/' ? href.pathname : (href.pathname ? ((base.authority && !base.pathname ? '/' : '') + base.pathname.slice(0, base.pathname.lastIndexOf('/') + 1) + href.pathname) : base.pathname)) +\r\n\t\t(href.protocol || href.authority || href.pathname ? href.search : (href.search || base.search)) +\r\n\t\thref.hash;\r\n}\r\n\r\nfunction getDocumentUri(uri) {\r\n\treturn uri.split('#')[0];\r\n}\r\nfunction normSchema(schema, baseUri) {\r\n\tif (schema && typeof schema === \"object\") {\r\n\t\tif (baseUri === undefined) {\r\n\t\t\tbaseUri = schema.id;\r\n\t\t} else if (typeof schema.id === \"string\") {\r\n\t\t\tbaseUri = resolveUrl(baseUri, schema.id);\r\n\t\t\tschema.id = baseUri;\r\n\t\t}\r\n\t\tif (Array.isArray(schema)) {\r\n\t\t\tfor (var i = 0; i < schema.length; i++) {\r\n\t\t\t\tnormSchema(schema[i], baseUri);\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tif (typeof schema['$ref'] === \"string\") {\r\n\t\t\t\tschema['$ref'] = resolveUrl(baseUri, schema['$ref']);\r\n\t\t\t}\r\n\t\t\tfor (var key in schema) {\r\n\t\t\t\tif (key !== \"enum\") {\r\n\t\t\t\t\tnormSchema(schema[key], baseUri);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\n\r\nfunction defaultErrorReporter(language) {\r\n\tlanguage = language || 'en';\r\n\r\n\tvar errorMessages = languages[language];\r\n\r\n\treturn function (error) {\r\n\t\tvar messageTemplate = errorMessages[error.code] || ErrorMessagesDefault[error.code];\r\n\t\tif (typeof messageTemplate !== 'string') {\r\n\t\t\treturn \"Unknown error code \" + error.code + \": \" + JSON.stringify(error.messageParams);\r\n\t\t}\r\n\t\tvar messageParams = error.params;\r\n\t\t// Adapted from Crockford's supplant()\r\n\t\treturn messageTemplate.replace(/\\{([^{}]*)\\}/g, function (whole, varName) {\r\n\t\t\tvar subValue = messageParams[varName];\r\n\t\t\treturn typeof subValue === 'string' || typeof subValue === 'number' ? subValue : whole;\r\n\t\t});\r\n\t};\r\n}\r\n\r\nvar ErrorCodes = {\r\n\tINVALID_TYPE: 0,\r\n\tENUM_MISMATCH: 1,\r\n\tANY_OF_MISSING: 10,\r\n\tONE_OF_MISSING: 11,\r\n\tONE_OF_MULTIPLE: 12,\r\n\tNOT_PASSED: 13,\r\n\t// Numeric errors\r\n\tNUMBER_MULTIPLE_OF: 100,\r\n\tNUMBER_MINIMUM: 101,\r\n\tNUMBER_MINIMUM_EXCLUSIVE: 102,\r\n\tNUMBER_MAXIMUM: 103,\r\n\tNUMBER_MAXIMUM_EXCLUSIVE: 104,\r\n\tNUMBER_NOT_A_NUMBER: 105,\r\n\t// String errors\r\n\tSTRING_LENGTH_SHORT: 200,\r\n\tSTRING_LENGTH_LONG: 201,\r\n\tSTRING_PATTERN: 202,\r\n\t// Object errors\r\n\tOBJECT_PROPERTIES_MINIMUM: 300,\r\n\tOBJECT_PROPERTIES_MAXIMUM: 301,\r\n\tOBJECT_REQUIRED: 302,\r\n\tOBJECT_ADDITIONAL_PROPERTIES: 303,\r\n\tOBJECT_DEPENDENCY_KEY: 304,\r\n\t// Array errors\r\n\tARRAY_LENGTH_SHORT: 400,\r\n\tARRAY_LENGTH_LONG: 401,\r\n\tARRAY_UNIQUE: 402,\r\n\tARRAY_ADDITIONAL_ITEMS: 403,\r\n\t// Custom/user-defined errors\r\n\tFORMAT_CUSTOM: 500,\r\n\tKEYWORD_CUSTOM: 501,\r\n\t// Schema structure\r\n\tCIRCULAR_REFERENCE: 600,\r\n\t// Non-standard validation options\r\n\tUNKNOWN_PROPERTY: 1000\r\n};\r\nvar ErrorCodeLookup = {};\r\nfor (var key in ErrorCodes) {\r\n\tErrorCodeLookup[ErrorCodes[key]] = key;\r\n}\r\nvar ErrorMessagesDefault = {\r\n\tINVALID_TYPE: \"Invalid type: {type} (expected {expected})\",\r\n\tENUM_MISMATCH: \"No enum match for: {value}\",\r\n\tANY_OF_MISSING: \"Data does not match any schemas from \\\"anyOf\\\"\",\r\n\tONE_OF_MISSING: \"Data does not match any schemas from \\\"oneOf\\\"\",\r\n\tONE_OF_MULTIPLE: \"Data is valid against more than one schema from \\\"oneOf\\\": indices {index1} and {index2}\",\r\n\tNOT_PASSED: \"Data matches schema from \\\"not\\\"\",\r\n\t// Numeric errors\r\n\tNUMBER_MULTIPLE_OF: \"Value {value} is not a multiple of {multipleOf}\",\r\n\tNUMBER_MINIMUM: \"Value {value} is less than minimum {minimum}\",\r\n\tNUMBER_MINIMUM_EXCLUSIVE: \"Value {value} is equal to exclusive minimum {minimum}\",\r\n\tNUMBER_MAXIMUM: \"Value {value} is greater than maximum {maximum}\",\r\n\tNUMBER_MAXIMUM_EXCLUSIVE: \"Value {value} is equal to exclusive maximum {maximum}\",\r\n\tNUMBER_NOT_A_NUMBER: \"Value {value} is not a valid number\",\r\n\t// String errors\r\n\tSTRING_LENGTH_SHORT: \"String is too short ({length} chars), minimum {minimum}\",\r\n\tSTRING_LENGTH_LONG: \"String is too long ({length} chars), maximum {maximum}\",\r\n\tSTRING_PATTERN: \"String does not match pattern: {pattern}\",\r\n\t// Object errors\r\n\tOBJECT_PROPERTIES_MINIMUM: \"Too few properties defined ({propertyCount}), minimum {minimum}\",\r\n\tOBJECT_PROPERTIES_MAXIMUM: \"Too many properties defined ({propertyCount}), maximum {maximum}\",\r\n\tOBJECT_REQUIRED: \"Missing required property: {key}\",\r\n\tOBJECT_ADDITIONAL_PROPERTIES: \"Additional properties not allowed\",\r\n\tOBJECT_DEPENDENCY_KEY: \"Dependency failed - key must exist: {missing} (due to key: {key})\",\r\n\t// Array errors\r\n\tARRAY_LENGTH_SHORT: \"Array is too short ({length}), minimum {minimum}\",\r\n\tARRAY_LENGTH_LONG: \"Array is too long ({length}), maximum {maximum}\",\r\n\tARRAY_UNIQUE: \"Array items are not unique (indices {match1} and {match2})\",\r\n\tARRAY_ADDITIONAL_ITEMS: \"Additional items not allowed\",\r\n\t// Format errors\r\n\tFORMAT_CUSTOM: \"Format validation failed ({message})\",\r\n\tKEYWORD_CUSTOM: \"Keyword failed: {key} ({message})\",\r\n\t// Schema structure\r\n\tCIRCULAR_REFERENCE: \"Circular $refs: {urls}\",\r\n\t// Non-standard validation options\r\n\tUNKNOWN_PROPERTY: \"Unknown property (not in schema)\"\r\n};\r\n\r\nfunction ValidationError(code, params, dataPath, schemaPath, subErrors) {\r\n\tError.call(this);\r\n\tif (code === undefined) {\r\n\t\tthrow new Error (\"No error code supplied: \" + schemaPath);\r\n\t}\r\n\tthis.message = '';\r\n\tthis.params = params;\r\n\tthis.code = code;\r\n\tthis.dataPath = dataPath || \"\";\r\n\tthis.schemaPath = schemaPath || \"\";\r\n\tthis.subErrors = subErrors || null;\r\n\r\n\tvar err = new Error(this.message);\r\n\tthis.stack = err.stack || err.stacktrace;\r\n\tif (!this.stack) {\r\n\t\ttry {\r\n\t\t\tthrow err;\r\n\t\t}\r\n\t\tcatch(err) {\r\n\t\t\tthis.stack = err.stack || err.stacktrace;\r\n\t\t}\r\n\t}\r\n}\r\nValidationError.prototype = Object.create(Error.prototype);\r\nValidationError.prototype.constructor = ValidationError;\r\nValidationError.prototype.name = 'ValidationError';\r\n\r\nValidationError.prototype.prefixWith = function (dataPrefix, schemaPrefix) {\r\n\tif (dataPrefix !== null) {\r\n\t\tdataPrefix = dataPrefix.replace(/~/g, \"~0\").replace(/\\//g, \"~1\");\r\n\t\tthis.dataPath = \"/\" + dataPrefix + this.dataPath;\r\n\t}\r\n\tif (schemaPrefix !== null) {\r\n\t\tschemaPrefix = schemaPrefix.replace(/~/g, \"~0\").replace(/\\//g, \"~1\");\r\n\t\tthis.schemaPath = \"/\" + schemaPrefix + this.schemaPath;\r\n\t}\r\n\tif (this.subErrors !== null) {\r\n\t\tfor (var i = 0; i < this.subErrors.length; i++) {\r\n\t\t\tthis.subErrors[i].prefixWith(dataPrefix, schemaPrefix);\r\n\t\t}\r\n\t}\r\n\treturn this;\r\n};\r\n\r\nfunction isTrustedUrl(baseUrl, testUrl) {\r\n\tif(testUrl.substring(0, baseUrl.length) === baseUrl){\r\n\t\tvar remainder = testUrl.substring(baseUrl.length);\r\n\t\tif ((testUrl.length > 0 && testUrl.charAt(baseUrl.length - 1) === \"/\")\r\n\t\t\t|| remainder.charAt(0) === \"#\"\r\n\t\t\t|| remainder.charAt(0) === \"?\") {\r\n\t\t\treturn true;\r\n\t\t}\r\n\t}\r\n\treturn false;\r\n}\r\n\r\nvar languages = {};\r\nfunction createApi(language) {\r\n\tvar globalContext = new ValidatorContext();\r\n\tvar currentLanguage;\r\n\tvar customErrorReporter;\r\n\tvar api = {\r\n\t\tsetErrorReporter: function (reporter) {\r\n\t\t\tif (typeof reporter === 'string') {\r\n\t\t\t\treturn this.language(reporter);\r\n\t\t\t}\r\n\t\t\tcustomErrorReporter = reporter;\r\n\t\t\treturn true;\r\n\t\t},\r\n\t\taddFormat: function () {\r\n\t\t\tglobalContext.addFormat.apply(globalContext, arguments);\r\n\t\t},\r\n\t\tlanguage: function (code) {\r\n\t\t\tif (!code) {\r\n\t\t\t\treturn currentLanguage;\r\n\t\t\t}\r\n\t\t\tif (!languages[code]) {\r\n\t\t\t\tcode = code.split('-')[0]; // fall back to base language\r\n\t\t\t}\r\n\t\t\tif (languages[code]) {\r\n\t\t\t\tcurrentLanguage = code;\r\n\t\t\t\treturn code; // so you can tell if fall-back has happened\r\n\t\t\t}\r\n\t\t\treturn false;\r\n\t\t},\r\n\t\taddLanguage: function (code, messageMap) {\r\n\t\t\tvar key;\r\n\t\t\tfor (key in ErrorCodes) {\r\n\t\t\t\tif (messageMap[key] && !messageMap[ErrorCodes[key]]) {\r\n\t\t\t\t\tmessageMap[ErrorCodes[key]] = messageMap[key];\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tvar rootCode = code.split('-')[0];\r\n\t\t\tif (!languages[rootCode]) { // use for base language if not yet defined\r\n\t\t\t\tlanguages[code] = messageMap;\r\n\t\t\t\tlanguages[rootCode] = messageMap;\r\n\t\t\t} else {\r\n\t\t\t\tlanguages[code] = Object.create(languages[rootCode]);\r\n\t\t\t\tfor (key in messageMap) {\r\n\t\t\t\t\tif (typeof languages[rootCode][key] === 'undefined') {\r\n\t\t\t\t\t\tlanguages[rootCode][key] = messageMap[key];\r\n\t\t\t\t\t}\r\n\t\t\t\t\tlanguages[code][key] = messageMap[key];\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\treturn this;\r\n\t\t},\r\n\t\tfreshApi: function (language) {\r\n\t\t\tvar result = createApi();\r\n\t\t\tif (language) {\r\n\t\t\t\tresult.language(language);\r\n\t\t\t}\r\n\t\t\treturn result;\r\n\t\t},\r\n\t\tvalidate: function (data, schema, checkRecursive, banUnknownProperties) {\r\n\t\t\tvar def = defaultErrorReporter(currentLanguage);\r\n\t\t\tvar errorReporter = customErrorReporter ? function (error, data, schema) {\r\n\t\t\t\treturn customErrorReporter(error, data, schema) || def(error, data, schema);\r\n\t\t\t} : def;\r\n\t\t\tvar context = new ValidatorContext(globalContext, false, errorReporter, checkRecursive, banUnknownProperties);\r\n\t\t\tif (typeof schema === \"string\") {\r\n\t\t\t\tschema = {\"$ref\": schema};\r\n\t\t\t}\r\n\t\t\tcontext.addSchema(\"\", schema);\r\n\t\t\tvar error = context.validateAll(data, schema, null, null, \"\");\r\n\t\t\tif (!error && banUnknownProperties) {\r\n\t\t\t\terror = context.banUnknownProperties(data, schema);\r\n\t\t\t}\r\n\t\t\tthis.error = error;\r\n\t\t\tthis.missing = context.missing;\r\n\t\t\tthis.valid = (error === null);\r\n\t\t\treturn this.valid;\r\n\t\t},\r\n\t\tvalidateResult: function () {\r\n\t\t\tvar result = {toString: function () {\r\n\t\t\t\treturn this.valid ? 'valid' : this.error.message;\r\n\t\t\t}};\r\n\t\t\tthis.validate.apply(result, arguments);\r\n\t\t\treturn result;\r\n\t\t},\r\n\t\tvalidateMultiple: function (data, schema, checkRecursive, banUnknownProperties) {\r\n\t\t\tvar def = defaultErrorReporter(currentLanguage);\r\n\t\t\tvar errorReporter = customErrorReporter ? function (error, data, schema) {\r\n\t\t\t\treturn customErrorReporter(error, data, schema) || def(error, data, schema);\r\n\t\t\t} : def;\r\n\t\t\tvar context = new ValidatorContext(globalContext, true, errorReporter, checkRecursive, banUnknownProperties);\r\n\t\t\tif (typeof schema === \"string\") {\r\n\t\t\t\tschema = {\"$ref\": schema};\r\n\t\t\t}\r\n\t\t\tcontext.addSchema(\"\", schema);\r\n\t\t\tcontext.validateAll(data, schema, null, null, \"\");\r\n\t\t\tif (banUnknownProperties) {\r\n\t\t\t\tcontext.banUnknownProperties(data, schema);\r\n\t\t\t}\r\n\t\t\tvar result = {toString: function () {\r\n\t\t\t\treturn this.valid ? 'valid' : this.error.message;\r\n\t\t\t}};\r\n\t\t\tresult.errors = context.errors;\r\n\t\t\tresult.missing = context.missing;\r\n\t\t\tresult.valid = (result.errors.length === 0);\r\n\t\t\treturn result;\r\n\t\t},\r\n\t\taddSchema: function () {\r\n\t\t\treturn globalContext.addSchema.apply(globalContext, arguments);\r\n\t\t},\r\n\t\tgetSchema: function () {\r\n\t\t\treturn globalContext.getSchema.apply(globalContext, arguments);\r\n\t\t},\r\n\t\tgetSchemaMap: function () {\r\n\t\t\treturn globalContext.getSchemaMap.apply(globalContext, arguments);\r\n\t\t},\r\n\t\tgetSchemaUris: function () {\r\n\t\t\treturn globalContext.getSchemaUris.apply(globalContext, arguments);\r\n\t\t},\r\n\t\tgetMissingUris: function () {\r\n\t\t\treturn globalContext.getMissingUris.apply(globalContext, arguments);\r\n\t\t},\r\n\t\tdropSchemas: function () {\r\n\t\t\tglobalContext.dropSchemas.apply(globalContext, arguments);\r\n\t\t},\r\n\t\tdefineKeyword: function () {\r\n\t\t\tglobalContext.defineKeyword.apply(globalContext, arguments);\r\n\t\t},\r\n\t\tdefineError: function (codeName, codeNumber, defaultMessage) {\r\n\t\t\tif (typeof codeName !== 'string' || !/^[A-Z]+(_[A-Z]+)*$/.test(codeName)) {\r\n\t\t\t\tthrow new Error('Code name must be a string in UPPER_CASE_WITH_UNDERSCORES');\r\n\t\t\t}\r\n\t\t\tif (typeof codeNumber !== 'number' || codeNumber%1 !== 0 || codeNumber < 10000) {\r\n\t\t\t\tthrow new Error('Code number must be an integer > 10000');\r\n\t\t\t}\r\n\t\t\tif (typeof ErrorCodes[codeName] !== 'undefined') {\r\n\t\t\t\tthrow new Error('Error already defined: ' + codeName + ' as ' + ErrorCodes[codeName]);\r\n\t\t\t}\r\n\t\t\tif (typeof ErrorCodeLookup[codeNumber] !== 'undefined') {\r\n\t\t\t\tthrow new Error('Error code already used: ' + ErrorCodeLookup[codeNumber] + ' as ' + codeNumber);\r\n\t\t\t}\r\n\t\t\tErrorCodes[codeName] = codeNumber;\r\n\t\t\tErrorCodeLookup[codeNumber] = codeName;\r\n\t\t\tErrorMessagesDefault[codeName] = ErrorMessagesDefault[codeNumber] = defaultMessage;\r\n\t\t\tfor (var langCode in languages) {\r\n\t\t\t\tvar language = languages[langCode];\r\n\t\t\t\tif (language[codeName]) {\r\n\t\t\t\t\tlanguage[codeNumber] = language[codeNumber] || language[codeName];\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t\treset: function () {\r\n\t\t\tglobalContext.reset();\r\n\t\t\tthis.error = null;\r\n\t\t\tthis.missing = [];\r\n\t\t\tthis.valid = true;\r\n\t\t},\r\n\t\tmissing: [],\r\n\t\terror: null,\r\n\t\tvalid: true,\r\n\t\tnormSchema: normSchema,\r\n\t\tresolveUrl: resolveUrl,\r\n\t\tgetDocumentUri: getDocumentUri,\r\n\t\terrorCodes: ErrorCodes\r\n\t};\r\n\tapi.language(language || 'en');\r\n\treturn api;\r\n}\r\n\r\nvar tv4 = createApi();\r\ntv4.addLanguage('en-gb', ErrorMessagesDefault);\r\n\r\n//legacy property\r\ntv4.tv4 = tv4;\r\n\r\nreturn tv4; // used by _header.js to globalise.\r\n\r\n}));","/**\n * @class BaseClient.Types\n *\n * - Manages and validates types of remoteStorage objects, using JSON-LD and\n * JSON Schema\n * - Adds schema declaration/validation methods to BaseClient instances.\n **/\nvar Types = {\n /**\n * -> \n */\n uris: {},\n\n /**\n * Contains schema objects of all types known to the BaseClient instance\n *\n * -> \n */\n schemas: {},\n\n /**\n * -> \n */\n aliases: {},\n\n /**\n * Called via public function BaseClient.declareType()\n *\n * @private\n */\n declare: function(moduleName, alias, uri, schema) {\n var fullAlias = moduleName + '/' + alias;\n\n if (schema.extends) {\n var extendedAlias;\n var parts = schema.extends.split('/');\n if (parts.length === 1) {\n extendedAlias = moduleName + '/' + parts.shift();\n } else {\n extendedAlias = parts.join('/');\n }\n var extendedUri = this.uris[extendedAlias];\n if (! extendedUri) {\n throw \"Type '\" + fullAlias + \"' tries to extend unknown schema '\" + extendedAlias + \"'\";\n }\n schema.extends = this.schemas[extendedUri];\n }\n\n this.uris[fullAlias] = uri;\n this.aliases[uri] = fullAlias;\n this.schemas[uri] = schema;\n },\n\n resolveAlias: function(alias) {\n return this.uris[alias];\n },\n\n getSchema: function(uri) {\n return this.schemas[uri];\n },\n\n inScope: function(moduleName) {\n var ml = moduleName.length;\n var schemas = {};\n for (var alias in this.uris) {\n if (alias.substr(0, ml + 1) === moduleName + '/') {\n var uri = this.uris[alias];\n schemas[uri] = this.schemas[uri];\n }\n }\n return schemas;\n }\n};\n\nvar SchemaNotFound = function(uri) {\n var error = new Error(\"Schema not found: \" + uri);\n error.name = \"SchemaNotFound\";\n return error;\n};\n\nSchemaNotFound.prototype = Error.prototype;\n\nTypes.SchemaNotFound = SchemaNotFound;\n\nmodule.exports = Types;\n","/**\n * A cache which can propagate changes up to parent folders and generate new\n * revision ids for them. The generated revision id is consistent across\n * different sessions. The keys for the cache are case-insensitive.\n *\n * @param defaultValue {string} the value that is returned for all keys that\n * don't exist in the cache\n *\n * @class\n */\nfunction RevisionCache (defaultValue) {\n this.defaultValue = defaultValue;\n this._canPropagate = false;\n this._storage = { };\n this._itemsRev = {};\n this.activatePropagation();\n}\n\nRevisionCache.prototype = {\n /**\n * Get a value from the cache or defaultValue, if the key is not in the\n * cache\n */\n get (key) {\n key = key.toLowerCase();\n let stored = this._storage[key];\n if (typeof stored === 'undefined') {\n stored = this.defaultValue;\n this._storage[key] = stored;\n }\n return stored;\n },\n\n /**\n * Set a value\n */\n set (key, value) {\n key = key.toLowerCase();\n if (this._storage[key] === value) {\n return value;\n }\n this._storage[key] = value;\n if (!value) {\n delete this._itemsRev[key];\n }\n this._updateParentFolderItemRev(key, value);\n if (this._canPropagate) {\n this._propagate(key);\n }\n return value;\n },\n\n /**\n * Delete a value\n */\n delete: function (key) {\n return this.set(key, null);\n },\n\n /**\n * Disables automatic update of folder revisions when a key value is updated\n */\n deactivatePropagation () {\n this._canPropagate = false;\n return true;\n },\n\n /**\n * Enables automatic update of folder revisions when a key value is updated\n * and refreshes the folder revision ids for entire tree.\n */\n activatePropagation () {\n if (this._canPropagate) {\n return true;\n }\n this._generateFolderRev(\"/\");\n this._canPropagate = true;\n return true;\n },\n\n /**\n * Returns a hash code for a string.\n */\n _hashCode (str) {\n let hash = 0, i, chr;\n if (str.length === 0) {\n return hash;\n }\n for (i = 0; i < str.length; i++) {\n chr = str.charCodeAt(i);\n // eslint-disable-next-line no-bitwise\n hash = ((hash << 5) - hash) + chr;\n // eslint-disable-next-line no-bitwise\n hash |= 0; // Convert to 32bit integer\n }\n return hash;\n },\n\n /**\n * Takes an array of strings and returns a hash of the items\n */\n _generateHash (items) {\n // We sort the items before joining them to ensure correct hash generation\n // every time\n const files = items.sort().join('|');\n const hash = \"\"+this._hashCode(files);\n return hash;\n },\n\n /**\n * Update the revision of a key in it's parent folder data\n */\n _updateParentFolderItemRev (key, rev) {\n if (key !== '/') {\n const parentFolder = this._getParentFolder(key);\n if (!this._itemsRev[parentFolder]) {\n this._itemsRev[parentFolder] = {};\n }\n const parentFolderItemsRev = this._itemsRev[parentFolder];\n if (!rev) {\n delete parentFolderItemsRev[key];\n } else {\n parentFolderItemsRev[key] = rev;\n }\n //reset revision until root\n this._updateParentFolderItemRev(parentFolder, this.defaultValue);\n }\n },\n\n _getParentFolder (key) {\n return key.substr(0, key.lastIndexOf(\"/\",key.length - 2) + 1);\n },\n\n /**\n * Propagate the changes to the parent folders and generate new revision ids\n * for them\n */\n _propagate (key) {\n if (key !== '/') {\n const parentFolder = this._getParentFolder(key);\n const parentFolderItemsRev = this._itemsRev[parentFolder];\n const hashItems = [];\n for (let path in parentFolderItemsRev) {\n hashItems.push(parentFolderItemsRev[path]);\n }\n const newRev = this._generateHash(hashItems);\n this.set(parentFolder, newRev);\n }\n },\n\n /**\n * Generate revision id for a folder and it's subfolders, by hashing it's\n * listing\n */\n _generateFolderRev (folder) {\n const itemsRev = this._itemsRev[folder];\n let hash = this.defaultValue;\n if (itemsRev) {\n const hashItems = [];\n for (let path in itemsRev) {\n const isDir = path.substr(-1) === '/';\n let hashItem;\n if (isDir) {\n hashItem = this._generateFolderRev(path);\n } else {\n hashItem = itemsRev[path];\n }\n hashItems.push(hashItem);\n }\n if (hashItems.length > 0) {\n hash = this._generateHash(hashItems);\n }\n }\n this.set(folder, hash);\n return hash;\n }\n};\n\nmodule.exports = RevisionCache;\n","/* global define */\n/*!\n * webfinger.js\n * version 2.7.0\n * http://github.com/silverbucket/webfinger.js\n *\n * Developed and Maintained by:\n * Nick Jennings 2012\n *\n * webfinger.js is released under the AGPL (see LICENSE).\n *\n * You don't have to do anything special to choose one license or the other and you don't\n * have to notify anyone which license you are using.\n * Please see the corresponding license file for details of these licenses.\n * You are free to use, modify and distribute this software, but all copyright\n * information must remain.\n *\n */\n\nif (typeof fetch !== 'function' && typeof XMLHttpRequest !== 'function') {\n // XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest;\n XMLHttpRequest = require('xhr2');\n}\n\n(function (global) {\n\n // URI to property name map\n var LINK_URI_MAPS = {\n 'http://webfist.org/spec/rel': 'webfist',\n 'http://webfinger.net/rel/avatar': 'avatar',\n 'remotestorage': 'remotestorage',\n 'http://tools.ietf.org/id/draft-dejong-remotestorage': 'remotestorage',\n 'remoteStorage': 'remotestorage',\n 'http://www.packetizer.com/rel/share': 'share',\n 'http://webfinger.net/rel/profile-page': 'profile',\n 'me': 'profile',\n 'vcard': 'vcard',\n 'blog': 'blog',\n 'http://packetizer.com/rel/blog': 'blog',\n 'http://schemas.google.com/g/2010#updates-from': 'updates',\n 'https://camlistore.org/rel/server': 'camilstore'\n };\n\n var LINK_PROPERTIES = {\n 'avatar': [],\n 'remotestorage': [],\n 'blog': [],\n 'vcard': [],\n 'updates': [],\n 'share': [],\n 'profile': [],\n 'webfist': [],\n 'camlistore': []\n };\n\n // list of endpoints to try, fallback from beginning to end.\n var URIS = ['webfinger', 'host-meta', 'host-meta.json'];\n\n function generateErrorObject(obj) {\n obj.toString = function () {\n return this.message;\n };\n return obj;\n }\n\n // given a URL ensures it's HTTPS.\n // returns false for null string or non-HTTPS URL.\n function isSecure(url) {\n if (typeof url !== 'string') {\n return false;\n }\n var parts = url.split('://');\n if (parts[0] === 'https') {\n return true;\n }\n return false;\n }\n\n /**\n * Function: WebFinger\n *\n * WebFinger constructor\n *\n * Returns:\n *\n * return WebFinger object\n */\n function WebFinger(config) {\n if (typeof config !== 'object') {\n config = {};\n }\n\n this.config = {\n tls_only: (typeof config.tls_only !== 'undefined') ? config.tls_only : true,\n webfist_fallback: (typeof config.webfist_fallback !== 'undefined') ? config.webfist_fallback : false,\n uri_fallback: (typeof config.uri_fallback !== 'undefined') ? config.uri_fallback : false,\n request_timeout: (typeof config.request_timeout !== 'undefined') ? config.request_timeout : 10000\n };\n }\n\n // make an http request and look for JRD response, fails if request fails\n // or response not json.\n WebFinger.prototype.__fetchJRD = function (url, errorHandler, sucessHandler) {\n if (typeof fetch === 'function') {\n return this.__fetchJRD_fetch(url, errorHandler, sucessHandler);\n } else if (typeof XMLHttpRequest === 'function') {\n return this.__fetchJRD_XHR(url, errorHandler, sucessHandler);\n } else {\n throw new Error(\"add a polyfill for fetch or XMLHttpRequest\");\n }\n };\n WebFinger.prototype.__fetchJRD_fetch = function (url, errorHandler, sucessHandler) {\n var webfinger = this;\n var abortController;\n if (typeof AbortController === 'function') {\n abortController = new AbortController();\n }\n var networkPromise = fetch(url, {\n headers: {'Accept': 'application/jrd+json, application/json'},\n signal: abortController ? abortController.signal : undefined\n }).\n then(function (response) {\n if (response.ok) {\n return response.text();\n } else if (response.status === 404) {\n throw generateErrorObject({\n message: 'resource not found',\n url: url,\n status: response.status\n });\n } else { // other HTTP status (redirects are handled transparently)\n throw generateErrorObject({\n message: 'error during request',\n url: url,\n status: response.status\n });\n }\n },\n function (err) { // connection refused, etc.\n throw generateErrorObject({\n message: 'error during request',\n url: url,\n status: undefined,\n err: err\n })\n }).\n then(function (responseText) {\n if (webfinger.__isValidJSON(responseText)) {\n return responseText;\n } else {\n throw generateErrorObject({\n message: 'invalid json',\n url: url,\n status: undefined\n });\n }\n });\n\n var timeoutPromise = new Promise(function (resolve, reject) {\n setTimeout(function () {\n reject(generateErrorObject({\n message: 'request timed out',\n url: url,\n status: undefined\n }));\n if (abortController) {\n abortController.abort();\n }\n }, webfinger.config.request_timeout);\n });\n\n Promise.race([networkPromise, timeoutPromise]).\n then(function (responseText) {\n sucessHandler(responseText);\n }).catch(function (err) {\n errorHandler(err);\n });\n };\n WebFinger.prototype.__fetchJRD_XHR = function (url, errorHandler, sucessHandler) {\n var self = this;\n var handlerSpent = false;\n var xhr = new XMLHttpRequest();\n\n function __processState() {\n if (handlerSpent){\n return;\n }else{\n handlerSpent = true;\n }\n\n if (xhr.status === 200) {\n if (self.__isValidJSON(xhr.responseText)) {\n return sucessHandler(xhr.responseText);\n } else {\n return errorHandler(generateErrorObject({\n message: 'invalid json',\n url: url,\n status: xhr.status\n }));\n }\n } else if (xhr.status === 404) {\n return errorHandler(generateErrorObject({\n message: 'resource not found',\n url: url,\n status: xhr.status\n }));\n } else if ((xhr.status >= 301) && (xhr.status <= 302)) {\n var location = xhr.getResponseHeader('Location');\n if (isSecure(location)) {\n return __makeRequest(location); // follow redirect\n } else {\n return errorHandler(generateErrorObject({\n message: 'no redirect URL found',\n url: url,\n status: xhr.status\n }));\n }\n } else {\n return errorHandler(generateErrorObject({\n message: 'error during request',\n url: url,\n status: xhr.status\n }));\n }\n }\n\n function __makeRequest() {\n xhr.onreadystatechange = function () {\n if (xhr.readyState === 4) {\n __processState();\n }\n };\n\n xhr.onload = function () {\n __processState();\n };\n\n xhr.ontimeout = function () {\n return errorHandler(generateErrorObject({\n message: 'request timed out',\n url: url,\n status: xhr.status\n }));\n };\n\n xhr.open('GET', url, true);\n xhr.timeout = self.config.request_timeout;\n xhr.setRequestHeader('Accept', 'application/jrd+json, application/json');\n xhr.send();\n }\n\n return __makeRequest();\n };\n\n WebFinger.prototype.__isValidJSON = function (str) {\n try {\n JSON.parse(str);\n } catch (e) {\n return false;\n }\n return true;\n };\n\n WebFinger.prototype.__isLocalhost = function (host) {\n var local = /^localhost(\\.localdomain)?(\\:[0-9]+)?$/;\n return local.test(host);\n };\n\n // processes JRD object as if it's a webfinger response object\n // looks for known properties and adds them to profile datat struct.\n WebFinger.prototype.__processJRD = function (URL, JRD, errorHandler, successHandler) {\n var parsedJRD = JSON.parse(JRD);\n if ((typeof parsedJRD !== 'object') ||\n (typeof parsedJRD.links !== 'object')) {\n if (typeof parsedJRD.error !== 'undefined') {\n return errorHandler(generateErrorObject({ message: parsedJRD.error, request: URL }));\n } else {\n return errorHandler(generateErrorObject({ message: 'unknown response from server', request: URL }));\n }\n }\n\n var links = parsedJRD.links;\n if (!Array.isArray(links)) {\n links = [];\n }\n var result = { // webfinger JRD - object, json, and our own indexing\n object: parsedJRD,\n json: JRD,\n idx: {}\n };\n\n result.idx.properties = {\n 'name': undefined\n };\n result.idx.links = JSON.parse(JSON.stringify(LINK_PROPERTIES));\n\n // process links\n links.map(function (link, i) {\n if (LINK_URI_MAPS.hasOwnProperty(link.rel)) {\n if (result.idx.links[LINK_URI_MAPS[link.rel]]) {\n var entry = {};\n Object.keys(link).map(function (item, n) {\n entry[item] = link[item];\n });\n result.idx.links[LINK_URI_MAPS[link.rel]].push(entry);\n }\n }\n });\n\n // process properties\n var props = JSON.parse(JRD).properties;\n for (var key in props) {\n if (props.hasOwnProperty(key)) {\n if (key === 'http://packetizer.com/ns/name') {\n result.idx.properties.name = props[key];\n }\n }\n }\n return successHandler(result);\n };\n\n WebFinger.prototype.lookup = function (address, cb) {\n if (typeof address !== 'string') {\n throw new Error('first parameter must be a user address');\n } else if (typeof cb !== 'function') {\n throw new Error('second parameter must be a callback');\n }\n\n var self = this;\n var host = '';\n if (address.indexOf('://') > -1) {\n // other uri format\n host = address.replace(/ /g,'').split('/')[2];\n } else {\n // useraddress\n host = address.replace(/ /g,'').split('@')[1];\n }\n var uri_index = 0; // track which URIS we've tried already\n var protocol = 'https'; // we use https by default\n\n if (self.__isLocalhost(host)) {\n protocol = 'http';\n }\n\n function __buildURL() {\n var uri = '';\n if (! address.split('://')[1]) {\n // the URI has not been defined, default to acct\n uri = 'acct:';\n }\n return protocol + '://' + host + '/.well-known/' +\n URIS[uri_index] + '?resource=' + uri + address;\n }\n\n // control flow for failures, what to do in various cases, etc.\n function __fallbackChecks(err) {\n if ((self.config.uri_fallback) && (host !== 'webfist.org') && (uri_index !== URIS.length - 1)) { // we have uris left to try\n uri_index = uri_index + 1;\n return __call();\n } else if ((!self.config.tls_only) && (protocol === 'https')) { // try normal http\n uri_index = 0;\n protocol = 'http';\n return __call();\n } else if ((self.config.webfist_fallback) && (host !== 'webfist.org')) { // webfist attempt\n uri_index = 0;\n protocol = 'http';\n host = 'webfist.org';\n // webfist will\n // 1. make a query to the webfist server for the users account\n // 2. from the response, get a link to the actual webfinger json data\n // (stored somewhere in control of the user)\n // 3. make a request to that url and get the json\n // 4. process it like a normal webfinger response\n var URL = __buildURL();\n self.__fetchJRD(URL, cb, function (data) { // get link to users JRD\n self.__processJRD(URL, data, cb, function (result) {\n if ((typeof result.idx.links.webfist === 'object') &&\n (typeof result.idx.links.webfist[0].href === 'string')) {\n self.__fetchJRD(result.idx.links.webfist[0].href, cb, function (JRD) {\n self.__processJRD(URL, JRD, cb, function (result) {\n return cb(null, cb);\n });\n });\n }\n });\n });\n } else {\n return cb(err);\n }\n }\n\n function __call() {\n // make request\n var URL = __buildURL();\n self.__fetchJRD(URL, __fallbackChecks, function (JRD) {\n self.__processJRD(URL, JRD, cb, function (result) { cb(null, result); });\n });\n }\n\n return setTimeout(__call, 0);\n };\n\n WebFinger.prototype.lookupLink = function (address, rel, cb) {\n if (LINK_PROPERTIES.hasOwnProperty(rel)) {\n this.lookup(address, function (err, p) {\n var links = p.idx.links[rel];\n if (err) {\n return cb(err);\n } else if (links.length === 0) {\n return cb('no links found with rel=\"' + rel + '\"');\n } else {\n return cb(null, links[0]);\n }\n });\n } else {\n return cb('unsupported rel ' + rel);\n }\n };\n\n\n\n // AMD support\n if (typeof define === 'function' && define.amd) {\n define([], function () { return WebFinger; });\n // CommonJS and Node.js module support.\n } else if (typeof exports !== 'undefined') {\n // Support Node.js specific `module.exports` (which can be a function)\n if (typeof module !== 'undefined' && module.exports) {\n exports = module.exports = WebFinger;\n }\n // But always support CommonJS module 1.1.1 spec (`exports` cannot be a function)\n exports.WebFinger = WebFinger;\n } else {\n // browser