-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
--- /dev/null
+//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);
+ }
+});
--- /dev/null
+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
+ };
+});
--- /dev/null
+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
+ };
+
+});
--- /dev/null
+
+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
+ }
+});
--- /dev/null
+
+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
+ };
+});
+++ /dev/null
-//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);
- }
-});
+++ /dev/null
-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
- };
-});
+++ /dev/null
-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
- };
-
-});
+++ /dev/null
-
-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
- }
-});
+++ /dev/null
-
-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
- };
-});
+++ /dev/null
-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);
- }
- };
- });
-});
+++ /dev/null
-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='';
- }
- }
-});
+++ /dev/null
-#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);
-}
+++ /dev/null
-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);
+++ /dev/null
-/*
- * 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)}})();
-
+++ /dev/null
-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();
- }
-});
+++ /dev/null
-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
- };
-});
+++ /dev/null
-<!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>
+++ /dev/null
-<!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>
+++ /dev/null
-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;
- }
- }
-});
+++ /dev/null
-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
- }
-});
--- /dev/null
+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);
+ }
+ };
+ });
+});
--- /dev/null
+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='';
+ }
+ }
+});
--- /dev/null
+#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);
+}
--- /dev/null
+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();
+ }
+});
--- /dev/null
+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
+ };
+});
--- /dev/null
+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;
+ }
+ }
+});
--- /dev/null
+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
+ }
+});