restructure to a code package without any loading/bootstrapping and hosting details...
authormartin <martin@siarp.de>
Sun, 11 Dec 2011 23:35:57 +0000 (00:35 +0100)
committermartin <martin@siarp.de>
Sun, 11 Dec 2011 23:35:57 +0000 (00:35 +0100)
31 files changed:
README.md
ajax.js [new file with mode: 0644]
button.js [new file with mode: 0644]
controller.js [new file with mode: 0644]
couch.js [new file with mode: 0644]
dav.js [new file with mode: 0644]
files/ajax.js [deleted file]
files/button.js [deleted file]
files/controller.js [deleted file]
files/couch.js [deleted file]
files/dav.js [deleted file]
files/main.js [deleted file]
files/oauth.js [deleted file]
files/remoteStorage.css [deleted file]
files/remoteStorage.js [deleted file]
files/require.js [deleted file]
files/session.js [deleted file]
files/spinner.gif [deleted file]
files/sync.js [deleted file]
files/tests/1.html [deleted file]
files/tests/2.html [deleted file]
files/versioning.js [deleted file]
files/webfinger.js [deleted file]
main.js [new file with mode: 0644]
oauth.js [new file with mode: 0644]
remoteStorage.css [new file with mode: 0644]
session.js [new file with mode: 0644]
spinner.gif [new file with mode: 0644]
sync.js [new file with mode: 0644]
versioning.js [new file with mode: 0644]
webfinger.js [new file with mode: 0644]

index c420b8e..2b77b1e 100644 (file)
--- a/README.md
+++ b/README.md
@@ -1,13 +1,4 @@
-This repo is the home of our client-side library, which is http://unhosted.nodejitsu.com/remoteStorage.js (staging)
-and http://unhosted.org/remoteStorage.js (production).
-To run this on your localhost, type:
-node server.js
+This is the remoteStorage CommonJS package that can be used with any asynchronous module loader like RequireJS.
 
 If you have questions, go to http://webchat.freenode.net/?channels=unhosted and ask. If you don't get
 an immediate reply, email support @unhosted.org.
-
-
-Happy hacking!
-
-michiel
-@unhosted.org
diff --git a/ajax.js b/ajax.js
new file mode 100644 (file)
index 0000000..4029256
--- /dev/null
+++ b/ajax.js
@@ -0,0 +1,33 @@
+//implementing $.ajax() like a poor man's jQuery:
+
+      //////////
+     // ajax //
+    //////////
+
+define({
+  ajax: function(params) {
+    var xhr = new XMLHttpRequest();
+    if(!params.method) {
+      params.method='GET';
+    }
+    if(!params.data) {
+      params.data = null;
+    }
+    xhr.open(params.method, params.url, true);
+    if(params.headers) {
+      for(var header in params.headers) {
+        xhr.setRequestHeader(header, params.headers[header]);
+      }
+    }
+    xhr.onreadystatechange = function() {
+      if(xhr.readyState == 4) {
+        if(xhr.status == 200 || xhr.status == 201 || xhr.status == 204) {
+          params.success(xhr.responseText);
+        } else {
+          params.error(xhr.status);
+        }
+      }
+    }
+    xhr.send(params.data);
+  }
+});
diff --git a/button.js b/button.js
new file mode 100644 (file)
index 0000000..90b3516
--- /dev/null
+++ b/button.js
@@ -0,0 +1,114 @@
+define(function() {
+  var handlers = {};
+  var buttonState;
+    ////////
+   // UI //
+  ////////
+  function DisplayConnectionState(isConnected, userAddress) {
+    if(isConnected) {
+      //button to disconnect:
+      document.getElementById('userButton').value='Disconnect';
+      //display span:
+      document.getElementById('userAddress').style.display='inline';
+      document.getElementById('userAddress').innerHTML=userAddress;
+      //hide input:
+      document.getElementById('userAddressInput').style.display='none';
+      document.getElementById('userAddressInput').disabled='disabled';
+      buttonState = 'connected';
+    } else {
+      //button to Sign in:
+      document.getElementById('userButton').value='Sign in';
+      //display input:
+      document.getElementById('userAddressInput').value='';
+      document.getElementById('userAddressInput').style.display='inline';
+      document.getElementById('userAddressInput').disabled='';
+      //hide input:
+      document.getElementById('userAddress').style.display='none';
+      document.getElementById('userAddress').disabled='disabled';
+      buttonState = 'disconnected';
+    }
+  }
+
+  function InputKeyUp(el) {
+    if(el.value=='') {
+      document.getElementById('userButton').className='';
+      document.getElementById('userButton').disabled='disabled';
+      el.parentNode.style.opacity='.5';
+    } else {
+      document.getElementById('userButton').disabled='';
+      document.getElementById('userButton').className='green';
+      el.parentNode.style.opacity='1';
+    }
+  }
+  function SpanMouseOver(el) {
+    el.className='red';
+  }
+  function SpanMouseOut(el) {
+    el.className='';
+  }
+  function SpanClick(el) {
+    console.log('You are clicking the span man. Click the button instead!');
+  }
+  function ButtonClick(el) {
+    if(buttonState == 'connected') {
+      handlers['disconnect'](document.getElementById('userAddressInput').value);
+      //handlers['disconnect']('test@yourremotestorage.net');
+    } else {
+      handlers['connect'](document.getElementById('userAddressInput').value);
+      //handlers['connect']('test@yourremotestorage.net');
+    }
+  }
+
+  function show(isConnected, userAddress) {
+    if(!document.getElementById('remoteStorageDiv')) {
+      var divEl = document.createElement('div');
+      divEl.id = 'remoteStorageDiv';
+      var cssFilePath = 'http://unhosted.nodejitsu.com/remoteStorage.css';//FIXME: move this to some sort of config
+      // Make button global as we need it from inline code.
+      window.rsButton = this;
+      if(true) {
+      //if(false) {
+        divEl.innerHTML = '<link rel="stylesheet" href="'+cssFilePath+'" />'
+          +'<input id="userAddressInput" type="text" placeholder="you@yourremotestorage"'
+          +' onkeyup="if (event.keyCode == 13) {rsButton.trigger(\'ButtonClick\');'
+          +'  } else { rsButton.trigger(\'InputKeyUp\', this);}">'
+          +'<span id="userAddress" style="display:none"'
+          +' onmouseover="rsButton.trigger(\'SpanMouseOver\', this);"'
+          +' onmouseout="rsButton.trigger(\'SpanMouseOut\', this);"'
+          +' onclick="rsButton.trigger(\'SpanClick\', this)"></span>'
+          +'<input id="userButton" type="submit" value="Sign in"'
+          +' onclick="rsButton.trigger(\'ButtonClick\', this)">';
+      } else {
+        divEl.innerHTML = '<input id="usesInput" type="hidden">'
+          +'<input id="userAddress" type="hidden">'
+          +'<link rel="stylesheet" href="'+cssFilePath+'" />'
+          +'<img id="userButton" src="https://browserid.org/i/sign_in_blue.png" onclick="rsButton.trigger(\'ButtonClick\', this);">';
+      }
+      document.body.insertBefore(divEl, document.body.firstChild);
+    }
+    DisplayConnectionState(isConnected, userAddress);
+  }
+  function trigger(what, el) {
+    if(what == 'InputKeyUp') {
+      InputKeyUp(el);
+    } else if(what == 'SpanMouseOver') {
+      SpanMouseOver(el);
+    } else if(what == 'SpanMouseOut') {
+      SpanMouseOut(el);
+    } else if(what == 'SpanClick') {
+      SpanClick(el);
+    } else if(what == 'ButtonClick') {
+      ButtonClick(el);
+    } else {
+      alert('unhandled button trigger: '+what);
+    }
+  }
+  function on(what, cb) {
+    handlers[what] = cb;
+  }
+  return {
+    show: show,
+    trigger: trigger,
+    on: on
+  };
+});
diff --git a/controller.js b/controller.js
new file mode 100644 (file)
index 0000000..0039de1
--- /dev/null
@@ -0,0 +1,170 @@
+define([
+  'require',
+  './ajax',
+  './oauth',
+  './session',
+  './sync',
+  './versioning',
+  './webfinger',
+  './button'
+], function(require, ajax, oauth, session, sync, versioning, webfinger, button) {
+  var deadLine;
+  var working=false;
+  var intervalTimer;
+  var options = {
+    onChange: function(key, oldValue, newValue) {
+      console.log('item "'+key+'" changed from "'+oldValue+'" to "'+newValue+'"');
+      console.log('WARNING: Please configure an onChange function! Forcing full page refresh instead');
+      window.location = '';
+    },
+    category: location.host.replace('.', '_')
+  };
+  function onError(str) {
+    alert(str);
+  }
+  function connect(userAddress) {
+    document.getElementById('remoteStorageSpinner').style.display='inline';
+    if(true) {
+    //if(false) {
+      connectTo(userAddress);
+    } else {
+      navigator.id.getVerifiedEmail(function(assertion) {
+        console.log(assertion);
+        ajax.ajax({
+          url: 'http://myfavouritesandwich.org/browserid-verifier',
+          method: 'POST',
+          data: 'assertion='+assertion+'&audience='+window.location,
+          success: function(data) {
+            console.log(data);
+            connectTo(JSON.parse(data).email);
+          },
+          error: function(status) {
+            console.log('error status '+status);
+            document.getElementById('remoteStorageSpinner').style.display='none';
+          }
+        });
+      });
+    }
+  }
+  function connectTo(userAddress) {
+    webfinger.getAttributes(userAddress, {
+      allowHttpWebfinger: true,
+      allowSingleOriginWebfinger: false,
+      allowFakefinger: true
+    }, onError, function(attributes) {
+      var backendAddress = webfinger.resolveTemplate(attributes.template, options.category);
+      if(attributes.api == 'CouchDB') {
+        localStorage.setItem('_shadowBackendModuleName', 'couch');
+      } else if(attributes.api == 'WebDAV') {
+        localStorage.setItem('_shadowBackendModuleName', 'dav');
+      } else if(attributes.api == 'simple') {
+        localStorage.setItem('_shadowBackendModuleName', 'dav');
+      } else {
+        console.log('API "'+attributes.api+'" not supported! please try setting api="CouchDB" or "WebDAV" or "simple" in webfinger');
+      }
+      session.set('backendAddress', backendAddress);
+      oauth.go(attributes.auth, options.category, userAddress);
+    });
+  }
+  function disconnect() {
+    session.disconnect();
+    var isConnected = session.isConnected();
+    var userAddress = session.get('userAddress');
+    button.show(isConnected, userAddress);
+  }
+  function configure(setOptions) {
+    console.log(setOptions);
+    if(setOptions) {
+      for(var i in setOptions) {
+        options[i] = setOptions[i];
+      }
+    }
+  }
+  function needLoginBox() {
+    if(options.suppressDialog) {
+      return false;
+    } else {
+      return true;
+    }
+  }
+  function linkButtonToSession() {
+    var isConnected = session.isConnected();
+    var userAddress = session.get('userAddress');
+    if(needLoginBox()) {
+      button.on('connect', connect);
+      button.on('disconnect', disconnect);
+      button.show(isConnected, userAddress);
+    }
+  }
+  function afterLoadingBackend(backendObj) {
+    oauth.harvestToken(function(token) {
+      session.set('token', token);
+      if(backendObj) {
+        backendObj.init(session.get('backendAddress'), token);
+        console.log('set backendObj');
+      }
+      sync.start();
+    });
+    sync.setBackend(backendObj);
+    trigger('timer');
+    var autoSaveMilliseconds = 5000;//FIXME: move this to some sort of config
+    setInterval(function() {
+      trigger('timer');
+    }, autoSaveMilliseconds);
+    document.getElementById('remoteStorageSpinner').style.display='none';
+  }
+  
+  function onLoad(setOptions) {
+    configure(setOptions); 
+    if(needLoginBox()) {
+      linkButtonToSession();
+    }
+    var backendName = localStorage.getItem('_shadowBackendModuleName')
+    if(backendName) {
+      require(['./' + backendName], afterLoadingBackend);
+    } else {
+      console.log('no backend for sync');
+      afterLoadingBackend(null);
+    }
+  }
+  function trigger(event, cb) {
+    document.getElementById('remoteStorageSpinner').style.display='inline';
+    console.log(event);
+    if(!working) {
+      var newTimestamp = versioning.takeLocalSnapshot()
+      if(newTimestamp) {
+        console.log('changes detected');
+        if(session.isConnected()) {
+          console.log('pushing');
+          sync.push(newTimestamp);
+        } else {
+          console.log('not connected');
+        }
+      }
+      if(session.isConnected()) {
+        working = true;
+        sync.work(deadLine, function(incomingKey, incomingValue) {
+          console.log('incoming value "'+incomingValue+'" for key "'+incomingKey+'".');
+          var oldValue = localStorage.getItem(incomingKey);
+          versioning.incomingChange(incomingKey, incomingValue);
+          options.onChange(incomingKey, oldValue, incomingValue);
+        }, function() {
+          working = false;
+          if(cb) {
+            cb();
+            }
+        });
+      } else {
+        document.getElementById('remoteStorageSpinner').style.display='none';
+      }
+    } else {
+      console.log('still working?');
+    }
+  }
+  return {
+    configure: configure,
+    onLoad: onLoad,
+    trigger: trigger
+  };
+
+});
diff --git a/couch.js b/couch.js
new file mode 100644 (file)
index 0000000..47b6955
--- /dev/null
+++ b/couch.js
@@ -0,0 +1,67 @@
+
+define(['./ajax'], function(ajax) {
+    function keyToAddress(key) {
+      var i = 0;
+      while(i < key.length && key[i] =='u') {
+       i++;
+      }
+      if((i < key.length) && (key[i] == '_')) {
+        key = 'u'+key;
+      }
+      return localStorage.getItem('_shadowBackendAddress') + key;
+    }
+    function doCall(method, key, value, err, cb, deadLine) {
+      var ajaxObj = {
+        url: keyToAddress(key),
+        method: method,
+        error: err,
+        success: cb,
+        deadLine: deadLine
+      }
+      ajaxObj.headers= {Authorization: 'Bearer '+localStorage.getItem('_shadowBackendToken')};
+      ajaxObj.fields={withCredentials: 'true'};
+      if(method!='GET') {
+        ajaxObj.data=value;
+      }
+      ajax.ajax(ajaxObj);
+    }
+    function init(address, bearerToken) {
+      localStorage.setItem('_shadowBackendAddress', address);
+      localStorage.setItem('_shadowBackendToken', bearerToken);
+    }
+    function get(key, err, cb, deadLine) {
+      console.log('couch.get("'+key+'", err, cb, '+deadLine+');');
+      doCall('GET', key, null, err, function(str) {
+        var obj = JSON.parse(str);
+        localStorage.setItem('_shadowCouchRev_'+key, obj._rev);
+        cb(obj.value);
+      }, deadLine);
+    }
+    function set(key, value, err, cb, deadLine) {
+      console.log('couch.set("'+key+'", "'+value+'", err, cb, '+deadLine+');');
+      var revision = localStorage.getItem('_shadowCouchRev_'+key);
+      var obj = {
+        value: value
+      };
+      if(revision) {
+        obj._rev = revision;
+      }
+      doCall('PUT', key, JSON.stringify(obj), err, function(str) {
+        var obj = JSON.parse(str);
+        if(obj.rev) {
+          localStorage.setItem('_shadowCouchRev_'+key, obj.rev);
+        }
+        cb();
+      }, deadLine);
+    }
+    function remove(key, err, cb, deadLine) {
+      console.log('couch.remove("'+key+'", err, cb, '+deadLine+');');
+      doCall('DELETE', key, null, err, cb, deadLine);
+    }
+    return {
+      init: init,
+      set: set,
+      get: get,
+      remove: remove
+    }
+});
diff --git a/dav.js b/dav.js
new file mode 100644 (file)
index 0000000..dfeddde
--- /dev/null
+++ b/dav.js
@@ -0,0 +1,59 @@
+
+define(['./ajax'], function(ajax) {
+    function keyToAddress(key) {
+      var i = 0;
+      while(i < key.length && key[i] =='u') {
+       i++;
+      }
+      if((i < key.length) && (key[i] == '_')) {
+        key = 'u'+key;
+      }
+      return localStorage.getItem('_shadowBackendAddress') + key;
+    }
+    function doCall(method, key, value, err, cb, deadLine) {
+      var ajaxObj = {
+        url: keyToAddress(key),
+        method: method,
+        error: err,
+        success: cb,
+        deadLine: deadLine
+      }
+      //ajaxObj.headers= {Authorization: 'Bearer '+localStorage.getItem('_shadowBackendToken')};
+      ajaxObj.headers= {Authorization: 'Basic '+localStorage.getItem('_shadowBackendToken')};
+      ajaxObj.fields={withCredentials: 'true'};
+      if(method!='GET') {
+        ajaxObj.data=value;
+      }
+      ajax.ajax(ajaxObj);
+    }
+    function init(address, bearerToken) {
+      localStorage.setItem('_shadowBackendAddress', address);
+      localStorage.setItem('_shadowBackendToken', bearerToken);
+    }
+    function get(key, err, cb, deadLine) {
+      console.log('couch.get("'+key+'", err, cb, '+deadLine+');');
+      doCall('GET', key, null, err, function(str) {
+        var obj = JSON.parse(str);
+        cb(obj.value);
+      }, deadLine);
+    }
+    function set(key, value, err, cb, deadLine) {
+      console.log('couch.set("'+key+'", "'+value+'", err, cb, '+deadLine+');');
+      var obj = {
+        value: value
+      };
+      doCall('PUT', key, JSON.stringify(obj), err, function(str) {
+        cb();
+      }, deadLine);
+    }
+    function remove(key, err, cb, deadLine) {
+      console.log('couch.remove("'+key+'", err, cb, '+deadLine+');');
+      doCall('DELETE', key, null, err, cb, deadLine);
+    }
+    return {
+      init: init,
+      set: set,
+      get: get,
+      remove: remove
+    };
+});
diff --git a/files/ajax.js b/files/ajax.js
deleted file mode 100644 (file)
index 4029256..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-//implementing $.ajax() like a poor man's jQuery:
-
-      //////////
-     // ajax //
-    //////////
-
-define({
-  ajax: function(params) {
-    var xhr = new XMLHttpRequest();
-    if(!params.method) {
-      params.method='GET';
-    }
-    if(!params.data) {
-      params.data = null;
-    }
-    xhr.open(params.method, params.url, true);
-    if(params.headers) {
-      for(var header in params.headers) {
-        xhr.setRequestHeader(header, params.headers[header]);
-      }
-    }
-    xhr.onreadystatechange = function() {
-      if(xhr.readyState == 4) {
-        if(xhr.status == 200 || xhr.status == 201 || xhr.status == 204) {
-          params.success(xhr.responseText);
-        } else {
-          params.error(xhr.status);
-        }
-      }
-    }
-    xhr.send(params.data);
-  }
-});
diff --git a/files/button.js b/files/button.js
deleted file mode 100644 (file)
index 90b3516..0000000
+++ /dev/null
@@ -1,114 +0,0 @@
-define(function() {
-  var handlers = {};
-  var buttonState;
-    ////////
-   // UI //
-  ////////
-  function DisplayConnectionState(isConnected, userAddress) {
-    if(isConnected) {
-      //button to disconnect:
-      document.getElementById('userButton').value='Disconnect';
-      //display span:
-      document.getElementById('userAddress').style.display='inline';
-      document.getElementById('userAddress').innerHTML=userAddress;
-      //hide input:
-      document.getElementById('userAddressInput').style.display='none';
-      document.getElementById('userAddressInput').disabled='disabled';
-      buttonState = 'connected';
-    } else {
-      //button to Sign in:
-      document.getElementById('userButton').value='Sign in';
-      //display input:
-      document.getElementById('userAddressInput').value='';
-      document.getElementById('userAddressInput').style.display='inline';
-      document.getElementById('userAddressInput').disabled='';
-      //hide input:
-      document.getElementById('userAddress').style.display='none';
-      document.getElementById('userAddress').disabled='disabled';
-      buttonState = 'disconnected';
-    }
-  }
-
-  function InputKeyUp(el) {
-    if(el.value=='') {
-      document.getElementById('userButton').className='';
-      document.getElementById('userButton').disabled='disabled';
-      el.parentNode.style.opacity='.5';
-    } else {
-      document.getElementById('userButton').disabled='';
-      document.getElementById('userButton').className='green';
-      el.parentNode.style.opacity='1';
-    }
-  }
-  function SpanMouseOver(el) {
-    el.className='red';
-  }
-  function SpanMouseOut(el) {
-    el.className='';
-  }
-  function SpanClick(el) {
-    console.log('You are clicking the span man. Click the button instead!');
-  }
-  function ButtonClick(el) {
-    if(buttonState == 'connected') {
-      handlers['disconnect'](document.getElementById('userAddressInput').value);
-      //handlers['disconnect']('test@yourremotestorage.net');
-    } else {
-      handlers['connect'](document.getElementById('userAddressInput').value);
-      //handlers['connect']('test@yourremotestorage.net');
-    }
-  }
-
-  function show(isConnected, userAddress) {
-    if(!document.getElementById('remoteStorageDiv')) {
-      var divEl = document.createElement('div');
-      divEl.id = 'remoteStorageDiv';
-      var cssFilePath = 'http://unhosted.nodejitsu.com/remoteStorage.css';//FIXME: move this to some sort of config
-      // Make button global as we need it from inline code.
-      window.rsButton = this;
-      if(true) {
-      //if(false) {
-        divEl.innerHTML = '<link rel="stylesheet" href="'+cssFilePath+'" />'
-          +'<input id="userAddressInput" type="text" placeholder="you@yourremotestorage"'
-          +' onkeyup="if (event.keyCode == 13) {rsButton.trigger(\'ButtonClick\');'
-          +'  } else { rsButton.trigger(\'InputKeyUp\', this);}">'
-          +'<span id="userAddress" style="display:none"'
-          +' onmouseover="rsButton.trigger(\'SpanMouseOver\', this);"'
-          +' onmouseout="rsButton.trigger(\'SpanMouseOut\', this);"'
-          +' onclick="rsButton.trigger(\'SpanClick\', this)"></span>'
-          +'<input id="userButton" type="submit" value="Sign in"'
-          +' onclick="rsButton.trigger(\'ButtonClick\', this)">';
-      } else {
-        divEl.innerHTML = '<input id="usesInput" type="hidden">'
-          +'<input id="userAddress" type="hidden">'
-          +'<link rel="stylesheet" href="'+cssFilePath+'" />'
-          +'<img id="userButton" src="https://browserid.org/i/sign_in_blue.png" onclick="rsButton.trigger(\'ButtonClick\', this);">';
-      }
-      document.body.insertBefore(divEl, document.body.firstChild);
-    }
-    DisplayConnectionState(isConnected, userAddress);
-  }
-  function trigger(what, el) {
-    if(what == 'InputKeyUp') {
-      InputKeyUp(el);
-    } else if(what == 'SpanMouseOver') {
-      SpanMouseOver(el);
-    } else if(what == 'SpanMouseOut') {
-      SpanMouseOut(el);
-    } else if(what == 'SpanClick') {
-      SpanClick(el);
-    } else if(what == 'ButtonClick') {
-      ButtonClick(el);
-    } else {
-      alert('unhandled button trigger: '+what);
-    }
-  }
-  function on(what, cb) {
-    handlers[what] = cb;
-  }
-  return {
-    show: show,
-    trigger: trigger,
-    on: on
-  };
-});
diff --git a/files/controller.js b/files/controller.js
deleted file mode 100644 (file)
index 0039de1..0000000
+++ /dev/null
@@ -1,170 +0,0 @@
-define([
-  'require',
-  './ajax',
-  './oauth',
-  './session',
-  './sync',
-  './versioning',
-  './webfinger',
-  './button'
-], function(require, ajax, oauth, session, sync, versioning, webfinger, button) {
-  var deadLine;
-  var working=false;
-  var intervalTimer;
-  var options = {
-    onChange: function(key, oldValue, newValue) {
-      console.log('item "'+key+'" changed from "'+oldValue+'" to "'+newValue+'"');
-      console.log('WARNING: Please configure an onChange function! Forcing full page refresh instead');
-      window.location = '';
-    },
-    category: location.host.replace('.', '_')
-  };
-  function onError(str) {
-    alert(str);
-  }
-  function connect(userAddress) {
-    document.getElementById('remoteStorageSpinner').style.display='inline';
-    if(true) {
-    //if(false) {
-      connectTo(userAddress);
-    } else {
-      navigator.id.getVerifiedEmail(function(assertion) {
-        console.log(assertion);
-        ajax.ajax({
-          url: 'http://myfavouritesandwich.org/browserid-verifier',
-          method: 'POST',
-          data: 'assertion='+assertion+'&audience='+window.location,
-          success: function(data) {
-            console.log(data);
-            connectTo(JSON.parse(data).email);
-          },
-          error: function(status) {
-            console.log('error status '+status);
-            document.getElementById('remoteStorageSpinner').style.display='none';
-          }
-        });
-      });
-    }
-  }
-  function connectTo(userAddress) {
-    webfinger.getAttributes(userAddress, {
-      allowHttpWebfinger: true,
-      allowSingleOriginWebfinger: false,
-      allowFakefinger: true
-    }, onError, function(attributes) {
-      var backendAddress = webfinger.resolveTemplate(attributes.template, options.category);
-      if(attributes.api == 'CouchDB') {
-        localStorage.setItem('_shadowBackendModuleName', 'couch');
-      } else if(attributes.api == 'WebDAV') {
-        localStorage.setItem('_shadowBackendModuleName', 'dav');
-      } else if(attributes.api == 'simple') {
-        localStorage.setItem('_shadowBackendModuleName', 'dav');
-      } else {
-        console.log('API "'+attributes.api+'" not supported! please try setting api="CouchDB" or "WebDAV" or "simple" in webfinger');
-      }
-      session.set('backendAddress', backendAddress);
-      oauth.go(attributes.auth, options.category, userAddress);
-    });
-  }
-  function disconnect() {
-    session.disconnect();
-    var isConnected = session.isConnected();
-    var userAddress = session.get('userAddress');
-    button.show(isConnected, userAddress);
-  }
-  function configure(setOptions) {
-    console.log(setOptions);
-    if(setOptions) {
-      for(var i in setOptions) {
-        options[i] = setOptions[i];
-      }
-    }
-  }
-  function needLoginBox() {
-    if(options.suppressDialog) {
-      return false;
-    } else {
-      return true;
-    }
-  }
-  function linkButtonToSession() {
-    var isConnected = session.isConnected();
-    var userAddress = session.get('userAddress');
-    if(needLoginBox()) {
-      button.on('connect', connect);
-      button.on('disconnect', disconnect);
-      button.show(isConnected, userAddress);
-    }
-  }
-  function afterLoadingBackend(backendObj) {
-    oauth.harvestToken(function(token) {
-      session.set('token', token);
-      if(backendObj) {
-        backendObj.init(session.get('backendAddress'), token);
-        console.log('set backendObj');
-      }
-      sync.start();
-    });
-    sync.setBackend(backendObj);
-    trigger('timer');
-    var autoSaveMilliseconds = 5000;//FIXME: move this to some sort of config
-    setInterval(function() {
-      trigger('timer');
-    }, autoSaveMilliseconds);
-    document.getElementById('remoteStorageSpinner').style.display='none';
-  }
-  
-  function onLoad(setOptions) {
-    configure(setOptions); 
-    if(needLoginBox()) {
-      linkButtonToSession();
-    }
-    var backendName = localStorage.getItem('_shadowBackendModuleName')
-    if(backendName) {
-      require(['./' + backendName], afterLoadingBackend);
-    } else {
-      console.log('no backend for sync');
-      afterLoadingBackend(null);
-    }
-  }
-  function trigger(event, cb) {
-    document.getElementById('remoteStorageSpinner').style.display='inline';
-    console.log(event);
-    if(!working) {
-      var newTimestamp = versioning.takeLocalSnapshot()
-      if(newTimestamp) {
-        console.log('changes detected');
-        if(session.isConnected()) {
-          console.log('pushing');
-          sync.push(newTimestamp);
-        } else {
-          console.log('not connected');
-        }
-      }
-      if(session.isConnected()) {
-        working = true;
-        sync.work(deadLine, function(incomingKey, incomingValue) {
-          console.log('incoming value "'+incomingValue+'" for key "'+incomingKey+'".');
-          var oldValue = localStorage.getItem(incomingKey);
-          versioning.incomingChange(incomingKey, incomingValue);
-          options.onChange(incomingKey, oldValue, incomingValue);
-        }, function() {
-          working = false;
-          if(cb) {
-            cb();
-            }
-        });
-      } else {
-        document.getElementById('remoteStorageSpinner').style.display='none';
-      }
-    } else {
-      console.log('still working?');
-    }
-  }
-  return {
-    configure: configure,
-    onLoad: onLoad,
-    trigger: trigger
-  };
-
-});
diff --git a/files/couch.js b/files/couch.js
deleted file mode 100644 (file)
index 47b6955..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-
-define(['./ajax'], function(ajax) {
-    function keyToAddress(key) {
-      var i = 0;
-      while(i < key.length && key[i] =='u') {
-       i++;
-      }
-      if((i < key.length) && (key[i] == '_')) {
-        key = 'u'+key;
-      }
-      return localStorage.getItem('_shadowBackendAddress') + key;
-    }
-    function doCall(method, key, value, err, cb, deadLine) {
-      var ajaxObj = {
-        url: keyToAddress(key),
-        method: method,
-        error: err,
-        success: cb,
-        deadLine: deadLine
-      }
-      ajaxObj.headers= {Authorization: 'Bearer '+localStorage.getItem('_shadowBackendToken')};
-      ajaxObj.fields={withCredentials: 'true'};
-      if(method!='GET') {
-        ajaxObj.data=value;
-      }
-      ajax.ajax(ajaxObj);
-    }
-    function init(address, bearerToken) {
-      localStorage.setItem('_shadowBackendAddress', address);
-      localStorage.setItem('_shadowBackendToken', bearerToken);
-    }
-    function get(key, err, cb, deadLine) {
-      console.log('couch.get("'+key+'", err, cb, '+deadLine+');');
-      doCall('GET', key, null, err, function(str) {
-        var obj = JSON.parse(str);
-        localStorage.setItem('_shadowCouchRev_'+key, obj._rev);
-        cb(obj.value);
-      }, deadLine);
-    }
-    function set(key, value, err, cb, deadLine) {
-      console.log('couch.set("'+key+'", "'+value+'", err, cb, '+deadLine+');');
-      var revision = localStorage.getItem('_shadowCouchRev_'+key);
-      var obj = {
-        value: value
-      };
-      if(revision) {
-        obj._rev = revision;
-      }
-      doCall('PUT', key, JSON.stringify(obj), err, function(str) {
-        var obj = JSON.parse(str);
-        if(obj.rev) {
-          localStorage.setItem('_shadowCouchRev_'+key, obj.rev);
-        }
-        cb();
-      }, deadLine);
-    }
-    function remove(key, err, cb, deadLine) {
-      console.log('couch.remove("'+key+'", err, cb, '+deadLine+');');
-      doCall('DELETE', key, null, err, cb, deadLine);
-    }
-    return {
-      init: init,
-      set: set,
-      get: get,
-      remove: remove
-    }
-});
diff --git a/files/dav.js b/files/dav.js
deleted file mode 100644 (file)
index dfeddde..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-
-define(['./ajax'], function(ajax) {
-    function keyToAddress(key) {
-      var i = 0;
-      while(i < key.length && key[i] =='u') {
-       i++;
-      }
-      if((i < key.length) && (key[i] == '_')) {
-        key = 'u'+key;
-      }
-      return localStorage.getItem('_shadowBackendAddress') + key;
-    }
-    function doCall(method, key, value, err, cb, deadLine) {
-      var ajaxObj = {
-        url: keyToAddress(key),
-        method: method,
-        error: err,
-        success: cb,
-        deadLine: deadLine
-      }
-      //ajaxObj.headers= {Authorization: 'Bearer '+localStorage.getItem('_shadowBackendToken')};
-      ajaxObj.headers= {Authorization: 'Basic '+localStorage.getItem('_shadowBackendToken')};
-      ajaxObj.fields={withCredentials: 'true'};
-      if(method!='GET') {
-        ajaxObj.data=value;
-      }
-      ajax.ajax(ajaxObj);
-    }
-    function init(address, bearerToken) {
-      localStorage.setItem('_shadowBackendAddress', address);
-      localStorage.setItem('_shadowBackendToken', bearerToken);
-    }
-    function get(key, err, cb, deadLine) {
-      console.log('couch.get("'+key+'", err, cb, '+deadLine+');');
-      doCall('GET', key, null, err, function(str) {
-        var obj = JSON.parse(str);
-        cb(obj.value);
-      }, deadLine);
-    }
-    function set(key, value, err, cb, deadLine) {
-      console.log('couch.set("'+key+'", "'+value+'", err, cb, '+deadLine+');');
-      var obj = {
-        value: value
-      };
-      doCall('PUT', key, JSON.stringify(obj), err, function(str) {
-        cb();
-      }, deadLine);
-    }
-    function remove(key, err, cb, deadLine) {
-      console.log('couch.remove("'+key+'", err, cb, '+deadLine+');');
-      doCall('DELETE', key, null, err, cb, deadLine);
-    }
-    return {
-      init: init,
-      set: set,
-      get: get,
-      remove: remove
-    };
-});
diff --git a/files/main.js b/files/main.js
deleted file mode 100644 (file)
index 25a562f..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-define(function(require) {
-
-  //before doing anything else, display a spinner:
-  (function() {
-    var spinner = document.createElement('img');
-    spinner.setAttribute('id', 'remoteStorageSpinner');
-    spinner.setAttribute('src', 'http://unhosted.nodejitsu.com/spinner.gif');
-    spinner.setAttribute('style', 'position:fixed;right:3em;top:1em;z-index:99999;');
-    document.body.insertBefore(spinner, document.body.firstChild);
-  })();
-  require(['./controller'], function(controller) {
-    var config = {
-      jsFileName: 'remoteStorage.js',
-      modulesFilePath: 'http://unhosted.nodejitsu.com/'
-    };
-
-    //require('http://browserid.org/include.js');
-
-    var scripts = document.getElementsByTagName('script');
-    for(var i=0; i < scripts.length; i++) {
-      if((new RegExp(config.jsFileName+'$')).test(scripts[i].src)) {
-        var options = (new Function('return ' + scripts[i].innerHTML.replace(/\n|\r/g, '')))();
-        break;
-      }
-    }
-    controller.onLoad(options);
-
-    window.remoteStorage = {
-      syncNow: function() {
-        return controller.trigger('syncNow');
-      },
-      configure: function(obj) {
-        return controller.configure(obj);
-      }
-    };
-  });
-});
diff --git a/files/oauth.js b/files/oauth.js
deleted file mode 100644 (file)
index 8c12dff..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-define({
-    ///////////////////////////
-   // OAuth2 implicit grant //
-  ///////////////////////////
-
-  go: function(address, category, userAddress) {
-    var loc = encodeURIComponent((''+window.location).split('#')[0]);
-    window.location = address
-      + ((address.indexOf('?') == -1)?'?':'&')
-      + 'client_id=' + loc
-      + '&redirect_uri=' + loc
-      + '&scope=' + category
-      + '&user_address=' + userAddress
-      + '&response_type=token';
-  },
-  harvestToken: function(cb) {
-    if(location.hash.length == 0) {
-      return;
-    }
-    var params = location.hash.split('&');
-    var paramsToStay = [];
-    for(var i = 0; i < params.length; i++){
-      if(params[i].length && params[i][0] =='#') {
-        params[i] = params[i].substring(1);
-      }
-      var kv = params[i].split('=');
-      if(kv.length >= 2) {
-        if(kv[0]=='access_token') {
-          var token = unescape(kv[1]);//unescaping is needed in chrome, otherwise you get %3D%3D at the end instead of ==
-          for(var i = 2; i < kv.length; i++) {
-            token += '='+kv[i];
-          }
-          cb(token);
-        } else if(kv[0]=='token_type') {
-          //ignore silently
-        } else {
-          paramsToStay.push(params[i]);
-        }
-      } else {
-        paramsToStay.push(params[i]);
-      }
-    }
-    if(paramsToStay.length) {
-      window.location='#'+paramsToStay.join('&');
-    } else {
-      window.location='';
-    }
-  }
-});
diff --git a/files/remoteStorage.css b/files/remoteStorage.css
deleted file mode 100644 (file)
index 6dbb31b..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-#remoteStorageDiv { position:fixed; right:0; top:0; z-index:9999; opacity:.5; }
-#remoteStorageDiv:hover { opacity:1; }
-#remoteStorageDiv input {
-    display:inline; margin:.5em; padding:.5em .6em; font-size:.8em;
-       color:#333; text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);
-       border:1px solid #ccc; border-bottom-color:#bbb; -webkit-border-radius:.3em; -moz-border-radius:.3em; border-radius:.3em;
-       background:#e6e6e6 no-repeat;
-       background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#fff), color-stop(0.25, #fff), to(#e6e6e6));
-       background-image:-webkit-linear-gradient(#fff, #fff 0.25, #e6e6e6);
-       background-image:-moz-linear-gradient(#fff, #fff 0.25, #e6e6e6);
-       background-image:-ms-linear-gradient(#fff, #fff 0.25, #e6e6e6);
-       background-image:-o-linear-gradient(#fff, #fff 0.25, #e6e6e6);
-       background-image:linear-gradient(#fff, #fff 0.25, #e6e6e6);
-       -webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05); -moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05); box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);
-       -webkit-transition:0.1s linear all; -moz-transition:0.1s linear all; transition:0.1s linear all;
-}
-#remoteStorageDiv #userAddressInput {
-       background:#fff no-repeat; opacity:.7;
-       background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#e6e6e6), color-stop(0.25, #e6e6e6), to(#fff));
-       background-image:-webkit-linear-gradient(#e6e6e6, #e6e6e6 0.25, #fff);
-       background-image:-moz-linear-gradient(#e6e6e6, #e6e6e6 0.25, #fff);
-       background-image:-ms-linear-gradient(#e6e6e6, #e6e6e6 0.25, #fff);
-       background-image:-o-linear-gradient(#e6e6e6, #e6e6e6 0.25, #fff);
-       background-image:linear-gradient(#e6e6e6, #e6e6e6 0.25, #fff);
-       -webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05); -moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05); box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);
-       -webkit-transition:0.1s linear all; -moz-transition:0.1s linear all; transition:0.1s linear all;
-}
-#remoteStorageDiv #userAddressInput:hover { background-position:0 -1em; text-decoration:none; opacity:1; }
-#remoteStorageDiv #userButton { margin-left:-.5em; }
-#remoteStorageDiv .red {
-       color:#fff; text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);
-       border-color:#c43c35 #c43c35 #882a25; border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
-       background:#c43c35 repeat-x;
-       background-image:-khtml-gradient(linear, left top, left bottom, from(#ee5f5b), to(#c43c35));
-       background-image:-moz-linear-gradient(top, #ee5f5b, #c43c35);
-       background-image:-ms-linear-gradient(top, #ee5f5b, #c43c35);
-       background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee5f5b), color-stop(100%, #c43c35));
-       background-image:-webkit-linear-gradient(top, #ee5f5b, #c43c35);
-       background-image:-o-linear-gradient(top, #ee5f5b, #c43c35);
-       background-image:linear-gradient(top, #ee5f5b, #c43c35);
-       filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#c43c35',GradientType=0 );
-}
-#remoteStorageDiv .green {
-       color:#fff; text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);
-       border-color:#57a957 #57a957 #3d773d; border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
-       background:#57a957 repeat-x;
-       background-image:-khtml-gradient(linear, left top, left bottom, from(#62c462), to(#57a957));
-       background-image:-moz-linear-gradient(top, #62c462, #57a957);
-       background-image:-ms-linear-gradient(top, #62c462, #57a957);
-       background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #62c462), color-stop(100%, #57a957));
-       background-image:-webkit-linear-gradient(top, #62c462, #57a957);
-       background-image:-o-linear-gradient(top, #62c462, #57a957);
-       background-image:linear-gradient(top, #62c462, #57a957);
-}
-#remoteStorageDiv .syncing {
-       color:#fff; text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);
-       border-color:#339bb9 #339bb9 #22697d; border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
-       background:#339bb9 repeat-x;
-       background-image:-khtml-gradient(linear, left top, left bottom, from(#5bc0de), to(#339bb9));
-       background-image:-moz-linear-gradient(top, #5bc0de, #339bb9);
-       background-image:-ms-linear-gradient(top, #5bc0de, #339bb9);
-       background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #5bc0de), color-stop(100%, #339bb9));
-       background-image:-webkit-linear-gradient(top, #5bc0de, #339bb9);
-       background-image:-o-linear-gradient(top, #5bc0de, #339bb9);
-       background-image:linear-gradient(top, #5bc0de, #339bb9);
-}
diff --git a/files/remoteStorage.js b/files/remoteStorage.js
deleted file mode 100644 (file)
index 6f0715a..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-var s = document.createElement('script');
-s.setAttribute('src', 'http://unhosted.nodejitsu.com/require.js');
-s.setAttribute('data-main', 'http://unhosted.nodejitsu.com/main');
-document.head.appendChild(s);
diff --git a/files/require.js b/files/require.js
deleted file mode 100644 (file)
index dad0c6c..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- *  RequireJS 1.0.2 Copyright (c) 2010-2011, The Dojo Foundation All Rights Reserved.
- *   Available via the MIT or new BSD license.
- *    see: http://github.com/jrburke/requirejs for details
- *    */
-var requirejs,require,define;
-(function(){function J(a){return M.call(a)==="[object Function]"}function E(a){return M.call(a)==="[object Array]"}function Z(a,c,h){for(var k in c)if(!(k in K)&&(!(k in a)||h))a[k]=c[k];return d}function N(a,c,d){a=Error(c+"\nhttp://requirejs.org/docs/errors.html#"+a);if(d)a.originalError=d;return a}function $(a,c,d){var k,j,q;for(k=0;q=c[k];k++){q=typeof q==="string"?{name:q}:q;j=q.location;if(d&&(!j||j.indexOf("/")!==0&&j.indexOf(":")===-1))j=d+"/"+(j||q.name);a[q.name]={name:q.name,location:j||
-q.name,main:(q.main||"main").replace(ea,"").replace(aa,"")}}}function V(a,c){a.holdReady?a.holdReady(c):c?a.readyWait+=1:a.ready(!0)}function fa(a){function c(b,l){var f,a;if(b&&b.charAt(0)===".")if(l){p.pkgs[l]?l=[l]:(l=l.split("/"),l=l.slice(0,l.length-1));f=b=l.concat(b.split("/"));var c;for(a=0;c=f[a];a++)if(c===".")f.splice(a,1),a-=1;else if(c==="..")if(a===1&&(f[2]===".."||f[0]===".."))break;else a>0&&(f.splice(a-1,2),a-=2);a=p.pkgs[f=b[0]];b=b.join("/");a&&b===f+"/"+a.main&&(b=f)}else b.indexOf("./")===
-0&&(b=b.substring(2));return b}function h(b,l){var f=b?b.indexOf("!"):-1,a=null,d=l?l.name:null,i=b,e,h;f!==-1&&(a=b.substring(0,f),b=b.substring(f+1,b.length));a&&(a=c(a,d));b&&(a?e=(f=m[a])&&f.normalize?f.normalize(b,function(b){return c(b,d)}):c(b,d):(e=c(b,d),h=E[e],h||(h=g.nameToUrl(e,null,l),E[e]=h)));return{prefix:a,name:e,parentMap:l,url:h,originalName:i,fullName:a?a+"!"+(e||""):e}}function k(){var b=!0,l=p.priorityWait,f,a;if(l){for(a=0;f=l[a];a++)if(!s[f]){b=!1;break}b&&delete p.priorityWait}return b}
-function j(b,l,f){return function(){var a=ga.call(arguments,0),c;if(f&&J(c=a[a.length-1]))c.__requireJsBuild=!0;a.push(l);return b.apply(null,a)}}function q(b,l){var a=j(g.require,b,l);Z(a,{nameToUrl:j(g.nameToUrl,b),toUrl:j(g.toUrl,b),defined:j(g.requireDefined,b),specified:j(g.requireSpecified,b),isBrowser:d.isBrowser});return a}function o(b){var l,a,c,C=b.callback,i=b.map,e=i.fullName,ba=b.deps;c=b.listeners;if(C&&J(C)){if(p.catchError.define)try{a=d.execCb(e,b.callback,ba,m[e])}catch(k){l=k}else a=
-d.execCb(e,b.callback,ba,m[e]);if(e)(C=b.cjsModule)&&C.exports!==void 0&&C.exports!==m[e]?a=m[e]=b.cjsModule.exports:a===void 0&&b.usingExports?a=m[e]:(m[e]=a,F[e]&&(Q[e]=!0))}else e&&(a=m[e]=C,F[e]&&(Q[e]=!0));if(D[b.id])delete D[b.id],b.isDone=!0,g.waitCount-=1,g.waitCount===0&&(I=[]);delete R[e];if(d.onResourceLoad&&!b.placeholder)d.onResourceLoad(g,i,b.depArray);if(l)return a=(e?h(e).url:"")||l.fileName||l.sourceURL,c=l.moduleTree,l=N("defineerror",'Error evaluating module "'+e+'" at location "'+
-a+'":\n'+l+"\nfileName:"+a+"\nlineNumber: "+(l.lineNumber||l.line),l),l.moduleName=e,l.moduleTree=c,d.onError(l);for(l=0;C=c[l];l++)C(a)}function r(b,a){return function(f){b.depDone[a]||(b.depDone[a]=!0,b.deps[a]=f,b.depCount-=1,b.depCount||o(b))}}function u(b,a){var f=a.map,c=f.fullName,h=f.name,i=L[b]||(L[b]=m[b]),e;if(!a.loading)a.loading=!0,e=function(b){a.callback=function(){return b};o(a);s[a.id]=!0;w()},e.fromText=function(b,a){var l=O;s[b]=!1;g.scriptCount+=1;g.fake[b]=!0;l&&(O=!1);d.exec(a);
-l&&(O=!0);g.completeLoad(b)},c in m?e(m[c]):i.load(h,q(f.parentMap,!0),e,p)}function v(b){D[b.id]||(D[b.id]=b,I.push(b),g.waitCount+=1)}function B(b){this.listeners.push(b)}function t(b,a){var f=b.fullName,c=b.prefix,d=c?L[c]||(L[c]=m[c]):null,i,e;f&&(i=R[f]);if(!i&&(e=!0,i={id:(c&&!d?M++ +"__p@:":"")+(f||"__r@"+M++),map:b,depCount:0,depDone:[],depCallbacks:[],deps:[],listeners:[],add:B},y[i.id]=!0,f&&(!c||L[c])))R[f]=i;c&&!d?(f=t(h(c),!0),f.add(function(){var a=h(b.originalName,b.parentMap),a=t(a,
-!0);i.placeholder=!0;a.add(function(b){i.callback=function(){return b};o(i)})})):e&&a&&(s[i.id]=!1,g.paused.push(i),v(i));return i}function x(b,a,f,c){var b=h(b,c),d=b.name,i=b.fullName,e=t(b),k=e.id,j=e.deps,n;if(i){if(i in m||s[k]===!0||i==="jquery"&&p.jQuery&&p.jQuery!==f().fn.jquery)return;y[k]=!0;s[k]=!0;i==="jquery"&&f&&S(f())}e.depArray=a;e.callback=f;for(f=0;f<a.length;f++)if(k=a[f])k=h(k,d?b:c),n=k.fullName,a[f]=n,n==="require"?j[f]=q(b):n==="exports"?(j[f]=m[i]={},e.usingExports=!0):n===
-"module"?e.cjsModule=j[f]={id:d,uri:d?g.nameToUrl(d,null,c):void 0,exports:m[i]}:n in m&&!(n in D)&&(!(i in F)||i in F&&Q[n])?j[f]=m[n]:(i in F&&(F[n]=!0,delete m[n],T[k.url]=!1),e.depCount+=1,e.depCallbacks[f]=r(e,f),t(k,!0).add(e.depCallbacks[f]));e.depCount?v(e):o(e)}function n(b){x.apply(null,b)}function z(b,a){if(!b.isDone){var c=b.map.fullName,d=b.depArray,g,i,e,k;if(c){if(a[c])return m[c];a[c]=!0}if(d)for(g=0;g<d.length;g++)if(i=d[g])if((e=h(i).prefix)&&(k=D[e])&&z(k,a),(e=D[i])&&!e.isDone&&
-s[i])i=z(e,a),b.depCallbacks[g](i);return c?m[c]:void 0}}function A(){var b=p.waitSeconds*1E3,a=b&&g.startTime+b<(new Date).getTime(),b="",c=!1,h=!1,j;if(!(g.pausedCount>0)){if(p.priorityWait)if(k())w();else return;for(j in s)if(!(j in K)&&(c=!0,!s[j]))if(a)b+=j+" ";else{h=!0;break}if(c||g.waitCount){if(a&&b)return j=N("timeout","Load timeout for modules: "+b),j.requireType="timeout",j.requireModules=b,d.onError(j);if(h||g.scriptCount){if((G||ca)&&!W)W=setTimeout(function(){W=0;A()},50)}else{if(g.waitCount){for(H=
-0;b=I[H];H++)z(b,{});g.paused.length&&w();X<5&&(X+=1,A())}X=0;d.checkReadyState()}}}}var g,w,p={waitSeconds:7,baseUrl:"./",paths:{},pkgs:{},catchError:{}},P=[],y={require:!0,exports:!0,module:!0},E={},m={},s={},D={},I=[],T={},M=0,R={},L={},F={},Q={},Y=0;S=function(b){if(!g.jQuery&&(b=b||(typeof jQuery!=="undefined"?jQuery:null))&&!(p.jQuery&&b.fn.jquery!==p.jQuery)&&("holdReady"in b||"readyWait"in b))if(g.jQuery=b,n(["jquery",[],function(){return jQuery}]),g.scriptCount)V(b,!0),g.jQueryIncremented=
-!0};w=function(){var b,a,c,h,j,i;Y+=1;if(g.scriptCount<=0)g.scriptCount=0;for(;P.length;)if(b=P.shift(),b[0]===null)return d.onError(N("mismatch","Mismatched anonymous define() module: "+b[b.length-1]));else n(b);if(!p.priorityWait||k())for(;g.paused.length;){j=g.paused;g.pausedCount+=j.length;g.paused=[];for(h=0;b=j[h];h++)a=b.map,c=a.url,i=a.fullName,a.prefix?u(a.prefix,b):!T[c]&&!s[i]&&(d.load(g,i,c),c.indexOf("empty:")!==0&&(T[c]=!0));g.startTime=(new Date).getTime();g.pausedCount-=j.length}Y===
-1&&A();Y-=1};g={contextName:a,config:p,defQueue:P,waiting:D,waitCount:0,specified:y,loaded:s,urlMap:E,urlFetched:T,scriptCount:0,defined:m,paused:[],pausedCount:0,plugins:L,needFullExec:F,fake:{},fullExec:Q,managerCallbacks:R,makeModuleMap:h,normalize:c,configure:function(b){var a,c,d;b.baseUrl&&b.baseUrl.charAt(b.baseUrl.length-1)!=="/"&&(b.baseUrl+="/");a=p.paths;d=p.pkgs;Z(p,b,!0);if(b.paths){for(c in b.paths)c in K||(a[c]=b.paths[c]);p.paths=a}if((a=b.packagePaths)||b.packages){if(a)for(c in a)c in
-K||$(d,a[c],c);b.packages&&$(d,b.packages);p.pkgs=d}if(b.priority)c=g.requireWait,g.requireWait=!1,g.takeGlobalQueue(),w(),g.require(b.priority),w(),g.requireWait=c,p.priorityWait=b.priority;if(b.deps||b.callback)g.require(b.deps||[],b.callback)},requireDefined:function(b,a){return h(b,a).fullName in m},requireSpecified:function(b,a){return h(b,a).fullName in y},require:function(b,c,f){if(typeof b==="string"){if(J(c))return d.onError(N("requireargs","Invalid require call"));if(d.get)return d.get(g,
-b,c);c=h(b,c);b=c.fullName;return!(b in m)?d.onError(N("notloaded","Module name '"+c.fullName+"' has not been loaded yet for context: "+a)):m[b]}(b&&b.length||c)&&x(null,b,c,f);if(!g.requireWait)for(;!g.scriptCount&&g.paused.length;)g.takeGlobalQueue(),w();return g.require},takeGlobalQueue:function(){U.length&&(ha.apply(g.defQueue,[g.defQueue.length-1,0].concat(U)),U=[])},completeLoad:function(b){var a;for(g.takeGlobalQueue();P.length;)if(a=P.shift(),a[0]===null){a[0]=b;break}else if(a[0]===b)break;
-else n(a),a=null;a?n(a):n([b,[],b==="jquery"&&typeof jQuery!=="undefined"?function(){return jQuery}:null]);S();d.isAsync&&(g.scriptCount-=1);w();d.isAsync||(g.scriptCount-=1)},toUrl:function(a,c){var d=a.lastIndexOf("."),h=null;d!==-1&&(h=a.substring(d,a.length),a=a.substring(0,d));return g.nameToUrl(a,h,c)},nameToUrl:function(a,h,f){var j,k,i,e,m=g.config,a=c(a,f&&f.fullName);if(d.jsExtRegExp.test(a))h=a+(h?h:"");else{j=m.paths;k=m.pkgs;f=a.split("/");for(e=f.length;e>0;e--)if(i=f.slice(0,e).join("/"),
-j[i]){f.splice(0,e,j[i]);break}else if(i=k[i]){a=a===i.name?i.location+"/"+i.main:i.location;f.splice(0,e,a);break}h=f.join("/")+(h||".js");h=(h.charAt(0)==="/"||h.match(/^\w+:/)?"":m.baseUrl)+h}return m.urlArgs?h+((h.indexOf("?")===-1?"?":"&")+m.urlArgs):h}};g.jQueryCheck=S;g.resume=w;return g}function ia(){var a,c,d;if(n&&n.readyState==="interactive")return n;a=document.getElementsByTagName("script");for(c=a.length-1;c>-1&&(d=a[c]);c--)if(d.readyState==="interactive")return n=d;return null}var ja=
-/(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg,ka=/require\(\s*["']([^'"\s]+)["']\s*\)/g,ea=/^\.\//,aa=/\.js$/,M=Object.prototype.toString,r=Array.prototype,ga=r.slice,ha=r.splice,G=!!(typeof window!=="undefined"&&navigator&&document),ca=!G&&typeof importScripts!=="undefined",la=G&&navigator.platform==="PLAYSTATION 3"?/^complete$/:/^(complete|loaded)$/,da=typeof opera!=="undefined"&&opera.toString()==="[object Opera]",K={},t={},U=[],n=null,X=0,O=!1,d,r={},I,v,x,y,u,z,A,H,B,S,W;if(typeof define==="undefined"){if(typeof requirejs!==
-"undefined")if(J(requirejs))return;else r=requirejs,requirejs=void 0;typeof require!=="undefined"&&!J(require)&&(r=require,require=void 0);d=requirejs=function(a,c,d){var k="_",j;!E(a)&&typeof a!=="string"&&(j=a,E(c)?(a=c,c=d):a=[]);if(j&&j.context)k=j.context;d=t[k]||(t[k]=fa(k));j&&d.configure(j);return d.require(a,c)};d.config=function(a){return d(a)};require||(require=d);d.toUrl=function(a){return t._.toUrl(a)};d.version="1.0.2";d.jsExtRegExp=/^\/|:|\?|\.js$/;v=d.s={contexts:t,skipAsync:{}};if(d.isAsync=
-d.isBrowser=G)if(x=v.head=document.getElementsByTagName("head")[0],y=document.getElementsByTagName("base")[0])x=v.head=y.parentNode;d.onError=function(a){throw a;};d.load=function(a,c,h){d.resourcesReady(!1);a.scriptCount+=1;d.attach(h,a,c);if(a.jQuery&&!a.jQueryIncremented)V(a.jQuery,!0),a.jQueryIncremented=!0};define=function(a,c,d){var k,j;typeof a!=="string"&&(d=c,c=a,a=null);E(c)||(d=c,c=[]);!c.length&&J(d)&&d.length&&(d.toString().replace(ja,"").replace(ka,function(a,d){c.push(d)}),c=(d.length===
-1?["require"]:["require","exports","module"]).concat(c));if(O&&(k=I||ia()))a||(a=k.getAttribute("data-requiremodule")),j=t[k.getAttribute("data-requirecontext")];(j?j.defQueue:U).push([a,c,d])};define.amd={multiversion:!0,plugins:!0,jQuery:!0};d.exec=function(a){return eval(a)};d.execCb=function(a,c,d,k){return c.apply(k,d)};d.addScriptToDom=function(a){I=a;y?x.insertBefore(a,y):x.appendChild(a);I=null};d.onScriptLoad=function(a){var c=a.currentTarget||a.srcElement,h;if(a.type==="load"||c&&la.test(c.readyState))n=
-null,a=c.getAttribute("data-requirecontext"),h=c.getAttribute("data-requiremodule"),t[a].completeLoad(h),c.detachEvent&&!da?c.detachEvent("onreadystatechange",d.onScriptLoad):c.removeEventListener("load",d.onScriptLoad,!1)};d.attach=function(a,c,h,k,j,n){var o;if(G)return k=k||d.onScriptLoad,o=c&&c.config&&c.config.xhtml?document.createElementNS("http://www.w3.org/1999/xhtml","html:script"):document.createElement("script"),o.type=j||"text/javascript",o.charset="utf-8",o.async=!v.skipAsync[a],c&&o.setAttribute("data-requirecontext",
-c.contextName),o.setAttribute("data-requiremodule",h),o.attachEvent&&!da?(O=!0,n?o.onreadystatechange=function(){if(o.readyState==="loaded")o.onreadystatechange=null,o.attachEvent("onreadystatechange",k),n(o)}:o.attachEvent("onreadystatechange",k)):o.addEventListener("load",k,!1),o.src=a,n||d.addScriptToDom(o),o;else ca&&(importScripts(a),c.completeLoad(h));return null};if(G){u=document.getElementsByTagName("script");for(H=u.length-1;H>-1&&(z=u[H]);H--){if(!x)x=z.parentNode;if(A=z.getAttribute("data-main")){if(!r.baseUrl)u=
-A.split("/"),z=u.pop(),u=u.length?u.join("/")+"/":"./",r.baseUrl=u,A=z.replace(aa,"");r.deps=r.deps?r.deps.concat(A):[A];break}}}d.checkReadyState=function(){var a=v.contexts,c;for(c in a)if(!(c in K)&&a[c].waitCount)return;d.resourcesReady(!0)};d.resourcesReady=function(a){var c,h;d.resourcesDone=a;if(d.resourcesDone)for(h in a=v.contexts,a)if(!(h in K)&&(c=a[h],c.jQueryIncremented))V(c.jQuery,!1),c.jQueryIncremented=!1};d.pageLoaded=function(){if(document.readyState!=="complete")document.readyState=
-"complete"};if(G&&document.addEventListener&&!document.readyState)document.readyState="loading",window.addEventListener("load",d.pageLoaded,!1);d(r);if(d.isAsync&&typeof setTimeout!=="undefined")B=v.contexts[r.context||"_"],B.requireWait=!0,setTimeout(function(){B.requireWait=!1;B.takeGlobalQueue();B.jQueryCheck();B.scriptCount||B.resume();d.checkReadyState()},0)}})();
-
diff --git a/files/session.js b/files/session.js
deleted file mode 100644 (file)
index a7fc176..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-define({
-  set: function(key, value) {
-    sessionStorage.setItem('_remoteStorage_'+key, value);
-  },
-  get: function(key) {
-    return sessionStorage.getItem('_remoteStorage_'+key);
-  },
-  isConnected: function() {
-    return (this.get('token') != null);
-  },
-  disconnect: function() {
-    sessionStorage.clear();
-  }
-});
diff --git a/files/spinner.gif b/files/spinner.gif
deleted file mode 100644 (file)
index 0e5ef33..0000000
Binary files a/files/spinner.gif and /dev/null differ
diff --git a/files/sync.js b/files/sync.js
deleted file mode 100644 (file)
index f06e313..0000000
+++ /dev/null
@@ -1,200 +0,0 @@
-define(function() {
-  var syncBackend;
-  
-  function setBackend(backendToSet) {
-    syncBackend = backendToSet;
-  }
-  function start() {
-    localStorage.setItem('_shadowSyncStatus', 'pulling');
-    console.log('setting sync status to pulling');
-  }
-  function push() {
-    if(localStorage.getItem('_shadowSyncStatus') == 'pulling') {
-      console.log('will push after pulling is completed');
-    } else {
-      localStorage.setItem('_shadowSyncStatus', 'pushing');
-      console.log('setting sync status to pushing');
-    }
-  }
-  function getItemToPull(next) {
-    var itemToPull = localStorage.getItem('_shadowSyncCurrEntry');
-    if(next) {
-      if(itemToPull == null) {
-        itemToPull = -1;
-      }
-      var remote =JSON.parse(localStorage.getItem('_shadowRemote'));
-      var local =JSON.parse(localStorage.getItem('_shadowIndex'));
-      var keysArr = keys(remote);
-      if(!local) {
-        local = {};
-      }
-      // loop through keysArr, but starting at itemToPull + 1
-      for(var i = parseInt(itemToPull)+1; i < keysArr.length; i++) {
-        var thisKey = keysArr[i];
-        if((!local[thisKey]) || (remote[thisKey] > local[thisKey])) {
-           itemToPull = i;
-           localStorage.setItem('_shadowSyncCurrEntry', itemToPull);
-           return thisKey;
-        }
-      }
-      return false;
-    }
-    if(itemToPull == null) {
-        return '_shadowIndex';
-    } else {
-      return (keys(JSON.parse(localStorage.getItem('_shadowRemote'))))[itemToPull];
-    }
-  }
-  function updateLocalIndex(itemPulled) {
-    var remote = JSON.parse(localStorage.getItem('_shadowRemote'));
-    var local = JSON.parse(localStorage.getItem('_shadowIndex'));
-    if(!local) {
-      local = {};
-    }
-    local[itemPulled] = remote[itemPulled];
-    localStorage.setItem('_shadowItem', JSON.stringify(local));
-  }
-  function resumePulling(deadLine, cb, whenDone) {
-    console.log('resume pulling');
-    var itemToPull = getItemToPull(false);
-    if(!itemToPull) {
-      localStorage.setItem('_shadowSyncStatus', 'idle');
-    } else {
-      var remoteKeyName = itemToPull;
-      if(itemToPull != '_shadowIndex') {
-        remoteKeyName += '_'+JSON.parse(localStorage.getItem('_shadowRemote'))[itemToPull];
-      }
-      syncBackend.get(remoteKeyName, function(msg) {
-        console.log('error retrieving "'+remoteKeyName+'":'+msg);
-        if((remoteKeyName == '_shadowIndex') && (msg==404)) {
-          console.log('virgin remote');
-          localStorage.setItem('_shadowRemote', JSON.stringify({}));
-          localStorage.setItem('_shadowSyncStatus', 'pushing');
-        }
-        whenDone();
-      }, function(value) {
-        if(itemToPull == '_shadowIndex') {
-          localStorage.setItem('_shadowRemote', value);
-        } else {
-           updateLocalIndex(itemToPull);
-           cb(itemToPull, value);
-        }
-        var nextItem = getItemToPull(true);
-        if(nextItem) {
-          work(deadLine, cb, whenDone);
-        } else {
-          localStorage.setItem('_shadowSyncStatus', 'pushing');
-          whenDone();
-        }
-      }, deadLine);
-    }
-  }
-  //FIXME
-  function keys(obj) {
-    var keysArr = [];
-    for(var i in obj) {
-      keysArr.push(i);
-    }
-    return keysArr;
-  }
-
-  function getItemToPush(next) {
-    var local = JSON.parse(localStorage.getItem('_shadowIndex'));
-    var remote = JSON.parse(localStorage.getItem('_shadowRemote'));
-    var entryToPush = localStorage.getItem('_shadowSyncCurrEntry');
-    if(entryToPush == null) {
-      entryToPush = 0;//leave as null in localStorage, no use updating that
-    }
-    if(next) {
-      entryToPush++;
-      localStorage.setItem('_shadowSyncCurrEntry', entryToPush);
-    }
-    var localKeys = keys(local);
-    var remoteKeys = keys(remote);
-    var keysArr = [];
-    for(var i = 0; i < localKeys.length; i++) {
-      if((!remote[remoteKeys[i]]) || (local[localKeys[i]] > remote[remoteKeys[i]])) {
-        keysArr.push(localKeys[i]);
-      }
-    }
-    if(entryToPush < keysArr.length) {
-      return keysArr[entryToPush];
-    } else if(entryToPush == keysArr.length) {
-      return '_shadowIndex';
-    } else {
-      localStorage.removeItem('_shadowSyncCurrEntry');
-      return false;
-    }
-  }
-
-  function resumePushing(deadLine, whenDone) {
-    console.log('resume pushing');
-    var itemToPush = getItemToPush(false);
-    var remoteKeyName = itemToPush;
-    if(itemToPush != '_shadowIndex') {
-      remoteKeyName += '_'+JSON.parse(localStorage.getItem('_shadowIndex'))[itemToPush];
-    }
-    syncBackend.set(remoteKeyName, localStorage.getItem(itemToPush), function(msg) {
-      console.log('error putting '+itemToPush);
-      whenDone();
-    }, function() {
-      if(itemToPush == '_shadowIndex') {
-        //pushing was successful; prime cache:
-        localStorage.setItem('_shadowRemote', localStorage.getItem('_shadowIndex'));
-      }
-      if(getItemToPush(true)) {
-        work(deadLine, function() {
-          console.log('incoming changes should not happen when pushing!');
-        }, whenDone);
-      } else {
-        localStorage.setItem('_shadowSyncStatus', 'idle');
-        whenDone();
-      }
-    }, deadLine);
-  }
-  function compareIndices() {
-    var remote = JSON.parse(localStorage.getItem('_shadowRemote'));
-    var local = JSON.parse(localStorage.getItem('_shadowIndex'));
-    if(!remote) {
-      remote = {};
-    }
-    if(!local) {
-      local = {};
-    }
-    if(remote.length != local.length) {
-      return false;
-    }
-    for(var i = 0; i<remote.length; i++) {
-      if(remote[i] != local[i]) {
-        return false;
-      }
-    }
-    return true;
-  }
-  function work(deadLine, cbIncomingChange, whenDone) {
-    var now = (new Date().getTime());
-    if(deadLine < now) {
-      return;
-    }
-    console.log('sync working, '+(deadLine - now)+' milliseconds left:');
-    if(localStorage.getItem('_shadowSyncStatus') == 'pulling') {
-      resumePulling(deadLine, cbIncomingChange, whenDone);
-    } else if(localStorage.getItem('_shadowSyncStatus') == 'pushing') {
-      resumePushing(deadLine, whenDone);
-    } else {
-      if(compareIndices()) {//this is necessary for instance if operating state was lost with a page refresh, bug, or network problem
-        console.log('nothing to work on.');
-        document.getElementById('remoteStorageSpinner').style.display='none';
-      } else {
-        console.log('found differences between the indexes. bug?');
-      }
-      whenDone();
-    }
-  }
-  return {
-    setBackend: setBackend,
-    start: start,
-    push: push,
-    work: work
-  };
-});
diff --git a/files/tests/1.html b/files/tests/1.html
deleted file mode 100644 (file)
index d5d756d..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-  <head>
-    <script>
-      if(location.search == '') {
-        location='?test-run'+(new Date()).getTime();
-      }
-    </script>
-
-    <script src="http://unhosted.org/remoteStorage.js">{
-      category: location.search.substring(1),
-      onChange: function(key, oldValue, newValue) {
-        console.info('key '+key+' was '+oldValue+' but updated from remote to '+newValue);
-      }
-    }</script>
-    <script>
-      function partOne() {
-        localStorage.clear();
-        localStorage.setItem('foo', 'bar');
-      }
-      function partTwo() {
-        localStorage.setItem('foo2', 'bar2');
-      
-      }
-      function partThree() {
-        localStorage.clear();
-      }
-      function partFour() {
-        if(localStorage.getItem('foo') != 'bar') {
-          console.log('test 1 fail');
-        } else {
-          console.log('test 1 pass');
-        }
-        if(localStorage.getItem('foo2') != 'bar2') {
-          console.log('test 2 fail');
-        } else {
-          console.log('test 2 pass');
-        }
-      }
-      function startOver() {
-        localStorage.clear();
-        sessionStorage.clear();
-        location='?test-run'+(new Date()).getTime();
-      }
-    </script>
-  </head>
-  <body>
-    <input type="submit" onclick="partOne();" value="click me first">
-    then log in
-    <input type="submit" onclick="partTwo();" value="then click me">
-    then log out
-    <input type="submit" onclick="partThree();" value="then click me">
-    then log in again
-    <input type="submit" onclick="partFour();" value="then click me">
-    <br><input type="submit" onclick="startOver();" value="start over">
-  </body>
-</html>
diff --git a/files/tests/2.html b/files/tests/2.html
deleted file mode 100644 (file)
index 1fb507b..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-  <head>
-    <script src="http://unhosted.org/remoteStorage.js">{
-      category: '../Ate?st-app',
-      onChange: ['see if i care', function(key, oldValue, newValue) {
-        console.info('key '+key+' was '+oldValue+' but updated from remote to '+newValue);
-      }]
-    }</script>
-  </head>
-  <body>
-    log in and look out for errors.
-  </body>
-</html>
diff --git a/files/versioning.js b/files/versioning.js
deleted file mode 100644 (file)
index 2b680f2..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-define({
-  incomingChange: function(key, value) {
-    var now = ((new Date()).getTime())/1000;
-    var shadowIndex = JSON.parse(localStorage.getItem('_shadowIndex')) || {};
-    shadowIndex[key] = now;
-    localStorage.setItem('_shadowIndex', JSON.stringify(shadowIndex));
-    localStorage.setItem(key, value);
-  },
-  takeLocalSnapshot: function() {
-    var hasChanges = false;
-    var now = ((new Date()).getTime())/1000;
-    var shadowIndex = JSON.parse(localStorage.getItem('_shadowIndex')) || {};
-    var shadowRemote = JSON.parse(localStorage.getItem('_shadowRemote')) || {};
-    for(var i = 0; i<localStorage.length; i++) {
-      var thisKey = localStorage.key(i);
-      if(thisKey.substring(0, 7) != '_shadow') {
-        var val = localStorage.getItem(thisKey);
-        var shadowVal = localStorage.getItem('_shadowItem_'+thisKey);
-        if(val != shadowVal) {
-          localStorage.setItem('_shadowItem_'+thisKey, val);
-          console.log('storing local version of item "'+thisKey+'" @'+now);
-          shadowIndex[thisKey] = now;
-          hasChanges = true;
-        }
-      }
-    }
-    if(hasChanges) {
-      localStorage.setItem('_shadowIndex', JSON.stringify(shadowIndex));
-      console.log('storing local snapshot '+now);
-      return now;
-    } else {
-      return false;
-    }
-  }
-});
diff --git a/files/webfinger.js b/files/webfinger.js
deleted file mode 100644 (file)
index 469985a..0000000
+++ /dev/null
@@ -1,196 +0,0 @@
-define(['./ajax'], function(ajax) {
-
-    ///////////////
-   // Webfinger //
-  ///////////////
-
-  var options, userAddress, userName, host, templateParts;//this is all a bit messy, but there are a lot of callbacks here, so globals help us with that.
-  function getAttributes(ua, setOptions, error, cb){
-    options = setOptions;
-    userAddress = ua;
-    var parts = ua.split('@');
-    if(parts.length < 2) {
-      error('That is not a user address. There is no @-sign in it');
-    } else if(parts.length > 2) {
-      error('That is not a user address. There is more than one @-sign in it');
-    } else {
-      if(!(/^[\.0-9A-Za-z]+$/.test(parts[0]))) {
-        error('That is not a user address. There are non-dotalphanumeric symbols before the @-sign: "'+parts[0]+'"');
-      } else if(!(/^[\.0-9A-Za-z\-]+$/.test(parts[1]))) {
-        error('That is not a user address. There are non-dotalphanumeric symbols after the @-sign: "'+parts[1]+'"');
-      } else {
-        userName = parts[0];
-        host = parts[1];
-        //error('So far so good. Looking up https host-meta for '+host);
-        ajax.ajax({
-          //url: 'https://'+host+'/.well-known/host-meta',
-          url: 'http://'+host+'/.well-known/host-meta',
-          success: function(data) {
-            afterHostmetaSuccess(data, error, cb);
-          },
-          error: function(data) {
-            afterHttpsHostmetaError(data, error, cb);
-          }
-        });
-      }
-    }
-  }
-
-  function afterHttpsHostmetaError(data, error, cb) {
-    if(options.allowHttpWebfinger) {
-      console.log('Https Host-meta error. Trying http.');
-      ajax.ajax({
-        url: 'http://'+host+'/.well-known/host-meta',
-        success: function(data) {
-          afterHostmetaSuccess(data, error, cb);
-        },
-        error: function(data) {
-          afterHttpHostmetaError(data, error, cb);
-        }
-      });
-    } else {
-       afterHttpHostmetaError(data, error, cb);
-    }
-  }
-
-  function afterHttpHostmetaError(data, error, cb) {
-    if(options.allowSingleOriginWebfinger) {
-      console.log('Trying single origin webfinger through proxy');
-      ajax.ajax({
-        url: 'http://useraddress.net/single-origin-webfinger...really?'+userAddress,
-        success: function(data) {
-          afterLrddSuccess(data, error, cb);
-        },
-        error: function(data) {
-          afterProxyError(data, error, cb);
-        }
-      });
-    } else {
-      afterProxyError(data, error, cb);
-    }
-  }
-    
-  function afterProxyError(data, error, cb) {
-    if(options.allowFakefinger) {
-      console.log('Trying Fakefinger');
-      ajax.ajax({
-        url: 'http://useraddress.net/fakefinger?userAddress='+userAddress,
-        success: function(data) {
-          afterLrddSuccess(data, error, cb);
-        },
-        error: function(data) {
-          afterFakefingerError(data, error, cb);
-        }
-      });
-    } else {
-      afterFakefingerError(data, error, cb);
-    }
-  }
-  function afterFakefingerError() {
-    alert('user address "'+userAddress+'" doesn\'t seem to have remoteStorage linked to it');
-  }
-  function afterHostmetaSuccess(data, error, cb) {
-    data = (new DOMParser()).parseFromString(data, 'text/xml');
-    if(!data.getElementsByTagName) {
-      error('Host-meta is not an XML document, or doesnt have xml mimetype.');
-      return;
-    }
-    var linkTags = data.getElementsByTagName('Link');
-    if(linkTags.length == 0) {
-      error('no Link tags found in host-meta');
-    } else {
-      var lrddFound = false;
-      var errorStr = 'none of the Link tags have a lrdd rel-attribute';
-      for(var linkTagI = 0; linkTagI < linkTags.length; linkTagI++) {
-        for(var attrI = 0; attrI < linkTags[linkTagI].attributes.length; attrI++) {
-          var attr = linkTags[linkTagI].attributes[attrI];
-          if((attr.name=='rel') && (attr.value=='lrdd')) {
-            lrddFound = true;
-            errorStr = 'the first Link tag with a lrdd rel-attribute has no template-attribute';
-            for(var attrJ = 0; attrJ < linkTags[linkTagI].attributes.length; attrJ++) {
-              var attr2 = linkTags[linkTagI].attributes[attrJ];
-              if(attr2.name=='template') {
-                templateParts = attr2.value.split('{uri}');
-                if(templateParts.length == 2) {
-                  ajax.ajax({
-                    url: templateParts[0]+'acct:'+userAddress+templateParts[1],
-                    success: function(data) {afterLrddSuccess(data, error, cb);},
-                    error: function(data){afterLrddNoAcctError(data, error, cb);}
-                  });
-                } else {
-                  errorStr = 'the template doesn\'t contain "{uri}"';
-                }
-                break;
-              }
-            }
-            break;
-          }
-        }
-        if(lrddFound) {
-          break;
-        }
-      }
-      if(!lrddFound) {
-        error(errorStr);//todo: make this error flow nicer
-      }
-    }
-  }
-  function afterLrddNoAcctError() {
-    error('the template doesn\'t contain "{uri}"');
-  }
-  function afterLrddSuccess(data, error, cb) {
-    data = (new DOMParser()).parseFromString(data, 'text/xml');
-    if(!data.getElementsByTagName) {
-      error('Lrdd is not an XML document, or doesnt have xml mimetype.');
-      return;
-    }
-    var linkTags = data.getElementsByTagName('Link');
-    if(linkTags.length == 0) {
-      error('no Link tags found in lrdd');
-    } else {
-      var linkFound = false;
-      var errorStr = 'none of the Link tags have a remoteStorage rel-attribute';
-      for(var linkTagI = 0; linkTagI < linkTags.length; linkTagI++) {
-        var attributes = {};
-        for(var attrI = 0; attrI < linkTags[linkTagI].attributes.length; attrI++) {
-          var attr = linkTags[linkTagI].attributes[attrI];
-          if((attr.name=='rel') && (attr.value=='remoteStorage')) {
-            linkFound = true;
-            errorStr = 'the first Link tag with a dav rel-attribute has no template-attribute';
-            for(var attrJ = 0; attrJ < linkTags[linkTagI].attributes.length; attrJ++) {
-              var attr2 = linkTags[linkTagI].attributes[attrJ];
-              if(attr2.name=='template') {
-                attributes.template = attr2.value;
-              }
-              if(attr2.name=='auth') {
-                attributes.auth = attr2.value;
-              }
-              if(attr2.name=='api') {
-                attributes.api = attr2.value;
-              }
-            }
-            break;
-          }
-        }
-        if(linkFound) {
-          cb(attributes);
-          break;
-        }
-      }
-      if(!linkFound) {
-        error(errorStr);
-      }
-    }
-  }
-  function resolveTemplate(template, dataCategory) {
-    var parts = template.split('{category}');
-    if(parts.length != 2) {
-      return 'cannot-resolve-template:'+template;
-    }
-    return parts[0]+dataCategory+parts[1];
-  }
-  return {
-    getAttributes: getAttributes,
-    resolveTemplate: resolveTemplate
-  }
-});
diff --git a/main.js b/main.js
new file mode 100644 (file)
index 0000000..b52331b
--- /dev/null
+++ b/main.js
@@ -0,0 +1,36 @@
+define(function(require) {
+
+  //before doing anything else, display a spinner:
+  (function() {
+    var spinner = document.createElement('img');
+    spinner.setAttribute('id', 'remoteStorageSpinner');
+    spinner.setAttribute('src', require.toUrl('./spinner.gif'));
+    spinner.setAttribute('style', 'position:fixed;right:3em;top:1em;z-index:99999;');
+    document.body.insertBefore(spinner, document.body.firstChild);
+  })();
+  require(['./controller'], function(controller) {
+    var config = {
+      jsFileName: 'remoteStorage.js'
+    };
+
+    //require('http://browserid.org/include.js');
+
+    var scripts = document.getElementsByTagName('script');
+    for(var i=0; i < scripts.length; i++) {
+      if((new RegExp(config.jsFileName+'$')).test(scripts[i].src)) {
+        var options = (new Function('return ' + scripts[i].innerHTML.replace(/\n|\r/g, '')))();
+        break;
+      }
+    }
+    controller.onLoad(options);
+
+    window.remoteStorage = {
+      syncNow: function() {
+        return controller.trigger('syncNow');
+      },
+      configure: function(obj) {
+        return controller.configure(obj);
+      }
+    };
+  });
+});
diff --git a/oauth.js b/oauth.js
new file mode 100644 (file)
index 0000000..8c12dff
--- /dev/null
+++ b/oauth.js
@@ -0,0 +1,49 @@
+define({
+    ///////////////////////////
+   // OAuth2 implicit grant //
+  ///////////////////////////
+
+  go: function(address, category, userAddress) {
+    var loc = encodeURIComponent((''+window.location).split('#')[0]);
+    window.location = address
+      + ((address.indexOf('?') == -1)?'?':'&')
+      + 'client_id=' + loc
+      + '&redirect_uri=' + loc
+      + '&scope=' + category
+      + '&user_address=' + userAddress
+      + '&response_type=token';
+  },
+  harvestToken: function(cb) {
+    if(location.hash.length == 0) {
+      return;
+    }
+    var params = location.hash.split('&');
+    var paramsToStay = [];
+    for(var i = 0; i < params.length; i++){
+      if(params[i].length && params[i][0] =='#') {
+        params[i] = params[i].substring(1);
+      }
+      var kv = params[i].split('=');
+      if(kv.length >= 2) {
+        if(kv[0]=='access_token') {
+          var token = unescape(kv[1]);//unescaping is needed in chrome, otherwise you get %3D%3D at the end instead of ==
+          for(var i = 2; i < kv.length; i++) {
+            token += '='+kv[i];
+          }
+          cb(token);
+        } else if(kv[0]=='token_type') {
+          //ignore silently
+        } else {
+          paramsToStay.push(params[i]);
+        }
+      } else {
+        paramsToStay.push(params[i]);
+      }
+    }
+    if(paramsToStay.length) {
+      window.location='#'+paramsToStay.join('&');
+    } else {
+      window.location='';
+    }
+  }
+});
diff --git a/remoteStorage.css b/remoteStorage.css
new file mode 100644 (file)
index 0000000..6dbb31b
--- /dev/null
@@ -0,0 +1,66 @@
+#remoteStorageDiv { position:fixed; right:0; top:0; z-index:9999; opacity:.5; }
+#remoteStorageDiv:hover { opacity:1; }
+#remoteStorageDiv input {
+    display:inline; margin:.5em; padding:.5em .6em; font-size:.8em;
+       color:#333; text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);
+       border:1px solid #ccc; border-bottom-color:#bbb; -webkit-border-radius:.3em; -moz-border-radius:.3em; border-radius:.3em;
+       background:#e6e6e6 no-repeat;
+       background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#fff), color-stop(0.25, #fff), to(#e6e6e6));
+       background-image:-webkit-linear-gradient(#fff, #fff 0.25, #e6e6e6);
+       background-image:-moz-linear-gradient(#fff, #fff 0.25, #e6e6e6);
+       background-image:-ms-linear-gradient(#fff, #fff 0.25, #e6e6e6);
+       background-image:-o-linear-gradient(#fff, #fff 0.25, #e6e6e6);
+       background-image:linear-gradient(#fff, #fff 0.25, #e6e6e6);
+       -webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05); -moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05); box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);
+       -webkit-transition:0.1s linear all; -moz-transition:0.1s linear all; transition:0.1s linear all;
+}
+#remoteStorageDiv #userAddressInput {
+       background:#fff no-repeat; opacity:.7;
+       background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#e6e6e6), color-stop(0.25, #e6e6e6), to(#fff));
+       background-image:-webkit-linear-gradient(#e6e6e6, #e6e6e6 0.25, #fff);
+       background-image:-moz-linear-gradient(#e6e6e6, #e6e6e6 0.25, #fff);
+       background-image:-ms-linear-gradient(#e6e6e6, #e6e6e6 0.25, #fff);
+       background-image:-o-linear-gradient(#e6e6e6, #e6e6e6 0.25, #fff);
+       background-image:linear-gradient(#e6e6e6, #e6e6e6 0.25, #fff);
+       -webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05); -moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05); box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);
+       -webkit-transition:0.1s linear all; -moz-transition:0.1s linear all; transition:0.1s linear all;
+}
+#remoteStorageDiv #userAddressInput:hover { background-position:0 -1em; text-decoration:none; opacity:1; }
+#remoteStorageDiv #userButton { margin-left:-.5em; }
+#remoteStorageDiv .red {
+       color:#fff; text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);
+       border-color:#c43c35 #c43c35 #882a25; border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+       background:#c43c35 repeat-x;
+       background-image:-khtml-gradient(linear, left top, left bottom, from(#ee5f5b), to(#c43c35));
+       background-image:-moz-linear-gradient(top, #ee5f5b, #c43c35);
+       background-image:-ms-linear-gradient(top, #ee5f5b, #c43c35);
+       background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee5f5b), color-stop(100%, #c43c35));
+       background-image:-webkit-linear-gradient(top, #ee5f5b, #c43c35);
+       background-image:-o-linear-gradient(top, #ee5f5b, #c43c35);
+       background-image:linear-gradient(top, #ee5f5b, #c43c35);
+       filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#c43c35',GradientType=0 );
+}
+#remoteStorageDiv .green {
+       color:#fff; text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);
+       border-color:#57a957 #57a957 #3d773d; border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+       background:#57a957 repeat-x;
+       background-image:-khtml-gradient(linear, left top, left bottom, from(#62c462), to(#57a957));
+       background-image:-moz-linear-gradient(top, #62c462, #57a957);
+       background-image:-ms-linear-gradient(top, #62c462, #57a957);
+       background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #62c462), color-stop(100%, #57a957));
+       background-image:-webkit-linear-gradient(top, #62c462, #57a957);
+       background-image:-o-linear-gradient(top, #62c462, #57a957);
+       background-image:linear-gradient(top, #62c462, #57a957);
+}
+#remoteStorageDiv .syncing {
+       color:#fff; text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);
+       border-color:#339bb9 #339bb9 #22697d; border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+       background:#339bb9 repeat-x;
+       background-image:-khtml-gradient(linear, left top, left bottom, from(#5bc0de), to(#339bb9));
+       background-image:-moz-linear-gradient(top, #5bc0de, #339bb9);
+       background-image:-ms-linear-gradient(top, #5bc0de, #339bb9);
+       background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #5bc0de), color-stop(100%, #339bb9));
+       background-image:-webkit-linear-gradient(top, #5bc0de, #339bb9);
+       background-image:-o-linear-gradient(top, #5bc0de, #339bb9);
+       background-image:linear-gradient(top, #5bc0de, #339bb9);
+}
diff --git a/session.js b/session.js
new file mode 100644 (file)
index 0000000..a7fc176
--- /dev/null
@@ -0,0 +1,14 @@
+define({
+  set: function(key, value) {
+    sessionStorage.setItem('_remoteStorage_'+key, value);
+  },
+  get: function(key) {
+    return sessionStorage.getItem('_remoteStorage_'+key);
+  },
+  isConnected: function() {
+    return (this.get('token') != null);
+  },
+  disconnect: function() {
+    sessionStorage.clear();
+  }
+});
diff --git a/spinner.gif b/spinner.gif
new file mode 100644 (file)
index 0000000..0e5ef33
Binary files /dev/null and b/spinner.gif differ
diff --git a/sync.js b/sync.js
new file mode 100644 (file)
index 0000000..f06e313
--- /dev/null
+++ b/sync.js
@@ -0,0 +1,200 @@
+define(function() {
+  var syncBackend;
+  
+  function setBackend(backendToSet) {
+    syncBackend = backendToSet;
+  }
+  function start() {
+    localStorage.setItem('_shadowSyncStatus', 'pulling');
+    console.log('setting sync status to pulling');
+  }
+  function push() {
+    if(localStorage.getItem('_shadowSyncStatus') == 'pulling') {
+      console.log('will push after pulling is completed');
+    } else {
+      localStorage.setItem('_shadowSyncStatus', 'pushing');
+      console.log('setting sync status to pushing');
+    }
+  }
+  function getItemToPull(next) {
+    var itemToPull = localStorage.getItem('_shadowSyncCurrEntry');
+    if(next) {
+      if(itemToPull == null) {
+        itemToPull = -1;
+      }
+      var remote =JSON.parse(localStorage.getItem('_shadowRemote'));
+      var local =JSON.parse(localStorage.getItem('_shadowIndex'));
+      var keysArr = keys(remote);
+      if(!local) {
+        local = {};
+      }
+      // loop through keysArr, but starting at itemToPull + 1
+      for(var i = parseInt(itemToPull)+1; i < keysArr.length; i++) {
+        var thisKey = keysArr[i];
+        if((!local[thisKey]) || (remote[thisKey] > local[thisKey])) {
+           itemToPull = i;
+           localStorage.setItem('_shadowSyncCurrEntry', itemToPull);
+           return thisKey;
+        }
+      }
+      return false;
+    }
+    if(itemToPull == null) {
+        return '_shadowIndex';
+    } else {
+      return (keys(JSON.parse(localStorage.getItem('_shadowRemote'))))[itemToPull];
+    }
+  }
+  function updateLocalIndex(itemPulled) {
+    var remote = JSON.parse(localStorage.getItem('_shadowRemote'));
+    var local = JSON.parse(localStorage.getItem('_shadowIndex'));
+    if(!local) {
+      local = {};
+    }
+    local[itemPulled] = remote[itemPulled];
+    localStorage.setItem('_shadowItem', JSON.stringify(local));
+  }
+  function resumePulling(deadLine, cb, whenDone) {
+    console.log('resume pulling');
+    var itemToPull = getItemToPull(false);
+    if(!itemToPull) {
+      localStorage.setItem('_shadowSyncStatus', 'idle');
+    } else {
+      var remoteKeyName = itemToPull;
+      if(itemToPull != '_shadowIndex') {
+        remoteKeyName += '_'+JSON.parse(localStorage.getItem('_shadowRemote'))[itemToPull];
+      }
+      syncBackend.get(remoteKeyName, function(msg) {
+        console.log('error retrieving "'+remoteKeyName+'":'+msg);
+        if((remoteKeyName == '_shadowIndex') && (msg==404)) {
+          console.log('virgin remote');
+          localStorage.setItem('_shadowRemote', JSON.stringify({}));
+          localStorage.setItem('_shadowSyncStatus', 'pushing');
+        }
+        whenDone();
+      }, function(value) {
+        if(itemToPull == '_shadowIndex') {
+          localStorage.setItem('_shadowRemote', value);
+        } else {
+           updateLocalIndex(itemToPull);
+           cb(itemToPull, value);
+        }
+        var nextItem = getItemToPull(true);
+        if(nextItem) {
+          work(deadLine, cb, whenDone);
+        } else {
+          localStorage.setItem('_shadowSyncStatus', 'pushing');
+          whenDone();
+        }
+      }, deadLine);
+    }
+  }
+  //FIXME
+  function keys(obj) {
+    var keysArr = [];
+    for(var i in obj) {
+      keysArr.push(i);
+    }
+    return keysArr;
+  }
+
+  function getItemToPush(next) {
+    var local = JSON.parse(localStorage.getItem('_shadowIndex'));
+    var remote = JSON.parse(localStorage.getItem('_shadowRemote'));
+    var entryToPush = localStorage.getItem('_shadowSyncCurrEntry');
+    if(entryToPush == null) {
+      entryToPush = 0;//leave as null in localStorage, no use updating that
+    }
+    if(next) {
+      entryToPush++;
+      localStorage.setItem('_shadowSyncCurrEntry', entryToPush);
+    }
+    var localKeys = keys(local);
+    var remoteKeys = keys(remote);
+    var keysArr = [];
+    for(var i = 0; i < localKeys.length; i++) {
+      if((!remote[remoteKeys[i]]) || (local[localKeys[i]] > remote[remoteKeys[i]])) {
+        keysArr.push(localKeys[i]);
+      }
+    }
+    if(entryToPush < keysArr.length) {
+      return keysArr[entryToPush];
+    } else if(entryToPush == keysArr.length) {
+      return '_shadowIndex';
+    } else {
+      localStorage.removeItem('_shadowSyncCurrEntry');
+      return false;
+    }
+  }
+
+  function resumePushing(deadLine, whenDone) {
+    console.log('resume pushing');
+    var itemToPush = getItemToPush(false);
+    var remoteKeyName = itemToPush;
+    if(itemToPush != '_shadowIndex') {
+      remoteKeyName += '_'+JSON.parse(localStorage.getItem('_shadowIndex'))[itemToPush];
+    }
+    syncBackend.set(remoteKeyName, localStorage.getItem(itemToPush), function(msg) {
+      console.log('error putting '+itemToPush);
+      whenDone();
+    }, function() {
+      if(itemToPush == '_shadowIndex') {
+        //pushing was successful; prime cache:
+        localStorage.setItem('_shadowRemote', localStorage.getItem('_shadowIndex'));
+      }
+      if(getItemToPush(true)) {
+        work(deadLine, function() {
+          console.log('incoming changes should not happen when pushing!');
+        }, whenDone);
+      } else {
+        localStorage.setItem('_shadowSyncStatus', 'idle');
+        whenDone();
+      }
+    }, deadLine);
+  }
+  function compareIndices() {
+    var remote = JSON.parse(localStorage.getItem('_shadowRemote'));
+    var local = JSON.parse(localStorage.getItem('_shadowIndex'));
+    if(!remote) {
+      remote = {};
+    }
+    if(!local) {
+      local = {};
+    }
+    if(remote.length != local.length) {
+      return false;
+    }
+    for(var i = 0; i<remote.length; i++) {
+      if(remote[i] != local[i]) {
+        return false;
+      }
+    }
+    return true;
+  }
+  function work(deadLine, cbIncomingChange, whenDone) {
+    var now = (new Date().getTime());
+    if(deadLine < now) {
+      return;
+    }
+    console.log('sync working, '+(deadLine - now)+' milliseconds left:');
+    if(localStorage.getItem('_shadowSyncStatus') == 'pulling') {
+      resumePulling(deadLine, cbIncomingChange, whenDone);
+    } else if(localStorage.getItem('_shadowSyncStatus') == 'pushing') {
+      resumePushing(deadLine, whenDone);
+    } else {
+      if(compareIndices()) {//this is necessary for instance if operating state was lost with a page refresh, bug, or network problem
+        console.log('nothing to work on.');
+        document.getElementById('remoteStorageSpinner').style.display='none';
+      } else {
+        console.log('found differences between the indexes. bug?');
+      }
+      whenDone();
+    }
+  }
+  return {
+    setBackend: setBackend,
+    start: start,
+    push: push,
+    work: work
+  };
+});
diff --git a/versioning.js b/versioning.js
new file mode 100644 (file)
index 0000000..2b680f2
--- /dev/null
@@ -0,0 +1,35 @@
+define({
+  incomingChange: function(key, value) {
+    var now = ((new Date()).getTime())/1000;
+    var shadowIndex = JSON.parse(localStorage.getItem('_shadowIndex')) || {};
+    shadowIndex[key] = now;
+    localStorage.setItem('_shadowIndex', JSON.stringify(shadowIndex));
+    localStorage.setItem(key, value);
+  },
+  takeLocalSnapshot: function() {
+    var hasChanges = false;
+    var now = ((new Date()).getTime())/1000;
+    var shadowIndex = JSON.parse(localStorage.getItem('_shadowIndex')) || {};
+    var shadowRemote = JSON.parse(localStorage.getItem('_shadowRemote')) || {};
+    for(var i = 0; i<localStorage.length; i++) {
+      var thisKey = localStorage.key(i);
+      if(thisKey.substring(0, 7) != '_shadow') {
+        var val = localStorage.getItem(thisKey);
+        var shadowVal = localStorage.getItem('_shadowItem_'+thisKey);
+        if(val != shadowVal) {
+          localStorage.setItem('_shadowItem_'+thisKey, val);
+          console.log('storing local version of item "'+thisKey+'" @'+now);
+          shadowIndex[thisKey] = now;
+          hasChanges = true;
+        }
+      }
+    }
+    if(hasChanges) {
+      localStorage.setItem('_shadowIndex', JSON.stringify(shadowIndex));
+      console.log('storing local snapshot '+now);
+      return now;
+    } else {
+      return false;
+    }
+  }
+});
diff --git a/webfinger.js b/webfinger.js
new file mode 100644 (file)
index 0000000..545a191
--- /dev/null
@@ -0,0 +1,196 @@
+define(['./ajax'], function(ajax) {
+
+    ///////////////
+   // Webfinger //
+  ///////////////
+
+  var options, userAddress, userName, host, templateParts;//this is all a bit messy, but there are a lot of callbacks here, so globals help us with that.
+  function getAttributes(ua, setOptions, error, cb){
+    options = setOptions;
+    userAddress = ua;
+    var parts = ua.split('@');
+    if(parts.length < 2) {
+      error('That is not a user address. There is no @-sign in it');
+    } else if(parts.length > 2) {
+      error('That is not a user address. There is more than one @-sign in it');
+    } else {
+      if(!(/^[\.0-9A-Za-z]+$/.test(parts[0]))) {
+        error('That is not a user address. There are non-dotalphanumeric symbols before the @-sign: "'+parts[0]+'"');
+      } else if(!(/^[\.0-9A-Za-z\-]+$/.test(parts[1]))) {
+        error('That is not a user address. There are non-dotalphanumeric symbols after the @-sign: "'+parts[1]+'"');
+      } else {
+        userName = parts[0];
+        host = parts[1];
+        //error('So far so good. Looking up https host-meta for '+host);
+        ajax.ajax({
+          //url: 'https://'+host+'/.well-known/host-meta',
+          url: 'http://'+host+'/.well-known/host-meta',
+          success: function(data) {
+            afterHostmetaSuccess(data, error, cb);
+          },
+          error: function(data) {
+            afterHttpsHostmetaError(data, error, cb);
+          }
+        })
+      }
+    }
+  }
+
+  function afterHttpsHostmetaError(data, error, cb) {
+    if(options.allowHttpWebfinger) {
+      console.log('Https Host-meta error. Trying http.');
+      ajax.ajax({
+        url: 'http://'+host+'/.well-known/host-meta',
+        success: function(data) {
+          afterHostmetaSuccess(data, error, cb);
+        },
+        error: function(data) {
+          afterHttpHostmetaError(data, error, cb);
+        }
+      })
+    } else {
+       afterHttpHostmetaError(data, error, cb);
+    }
+  }
+
+  function afterHttpHostmetaError(data, error, cb) {
+    if(options.allowSingleOriginWebfinger) {
+      console.log('Trying single origin webfinger through proxy');
+      ajax.ajax({
+        url: 'http://useraddress.net/single-origin-webfinger...really?'+userAddress,
+        success: function(data) {
+          afterLrddSuccess(data, error, cb);
+        },
+        error: function(data) {
+          afterProxyError(data, error, cb);
+        }
+      });
+    } else {
+      afterProxyError(data, error, cb);
+    }
+  }
+    
+  function afterProxyError(data, error, cb) {
+    if(options.allowFakefinger) {
+      console.log('Trying Fakefinger');
+      ajax.ajax({
+        url: 'http://useraddress.net/fakefinger?userAddress='+userAddress,
+        success: function(data) {
+          afterLrddSuccess(data, error, cb);
+        },
+        error: function(data) {
+          afterFakefingerError(data, error, cb);
+        }
+      });
+    } else {
+      afterFakefingerError(data, error, cb);
+    }
+  }
+  function afterFakefingerError() {
+    alert('user address "'+userAddress+'" doesn\'t seem to have remoteStorage linked to it');
+  }
+  function afterHostmetaSuccess(data, error, cb) {
+    data = (new DOMParser()).parseFromString(data, 'text/xml');
+    if(!data.getElementsByTagName) {
+      error('Host-meta is not an XML document, or doesnt have xml mimetype.');
+      return;
+    }
+    var linkTags = data.getElementsByTagName('Link');
+    if(linkTags.length == 0) {
+      error('no Link tags found in host-meta');
+    } else {
+      var lrddFound = false;
+      var errorStr = 'none of the Link tags have a lrdd rel-attribute';
+      for(var linkTagI = 0; linkTagI < linkTags.length; linkTagI++) {
+        for(var attrI = 0; attrI < linkTags[linkTagI].attributes.length; attrI++) {
+          var attr = linkTags[linkTagI].attributes[attrI];
+          if((attr.name=='rel') && (attr.value=='lrdd')) {
+            lrddFound = true;
+            errorStr = 'the first Link tag with a lrdd rel-attribute has no template-attribute';
+            for(var attrJ = 0; attrJ < linkTags[linkTagI].attributes.length; attrJ++) {
+              var attr2 = linkTags[linkTagI].attributes[attrJ];
+              if(attr2.name=='template') {
+                templateParts = attr2.value.split('{uri}');
+                if(templateParts.length == 2) {
+                  ajax.ajax({
+                    url: templateParts[0]+'acct:'+userAddress+templateParts[1],
+                    success: function(data) {afterLrddSuccess(data, error, cb);},
+                    error: function(data){afterLrddNoAcctError(data, error, cb);}
+                  })
+                } else {
+                  errorStr = 'the template doesn\'t contain "{uri}"';
+                }
+                break;
+              }
+            }
+            break;
+          }
+        }
+        if(lrddFound) {
+          break;
+        }
+      }
+      if(!lrddFound) {
+        error(errorStr);//todo: make this error flow nicer
+      }
+    }
+  }
+  function afterLrddNoAcctError() {
+    error('the template doesn\'t contain "{uri}"');
+  }
+  function afterLrddSuccess(data, error, cb) {
+    data = (new DOMParser()).parseFromString(data, 'text/xml');
+    if(!data.getElementsByTagName) {
+      error('Lrdd is not an XML document, or doesnt have xml mimetype.');
+      return;
+    }
+    var linkTags = data.getElementsByTagName('Link');
+    if(linkTags.length == 0) {
+      error('no Link tags found in lrdd');
+    } else {
+      var linkFound = false;
+      var errorStr = 'none of the Link tags have a remoteStorage rel-attribute';
+      for(var linkTagI = 0; linkTagI < linkTags.length; linkTagI++) {
+        var attributes = {};
+        for(var attrI = 0; attrI < linkTags[linkTagI].attributes.length; attrI++) {
+          var attr = linkTags[linkTagI].attributes[attrI];
+          if((attr.name=='rel') && (attr.value=='remoteStorage')) {
+            linkFound = true;
+            errorStr = 'the first Link tag with a dav rel-attribute has no template-attribute';
+            for(var attrJ = 0; attrJ < linkTags[linkTagI].attributes.length; attrJ++) {
+              var attr2 = linkTags[linkTagI].attributes[attrJ];
+              if(attr2.name=='template') {
+                attributes.template = attr2.value;
+              }
+              if(attr2.name=='auth') {
+                attributes.auth = attr2.value;
+              }
+              if(attr2.name=='api') {
+                attributes.api = attr2.value;
+              }
+            }
+            break;
+          }
+        }
+        if(linkFound) {
+          cb(attributes);
+          break;
+        }
+      }
+      if(!linkFound) {
+        error(errorStr);
+      }
+    }
+  }
+  function resolveTemplate(template, dataCategory) {
+    var parts = template.split('{category}');
+    if(parts.length != 2) {
+      return 'cannot-resolve-template:'+template;
+    }
+    return parts[0]+dataCategory+parts[1];
+  }
+  return {
+    getAttributes: getAttributes,
+    resolveTemplate: resolveTemplate
+  }
+});