简体   繁体   中英

Javascript library for real-time and offline web app

I have a back-end server with a REST api. On the front-end, I use Angular.js. To handle the real-time part, I would like to use a third-party service such as Pusher.

I'm looking for a simple library that could handle the M of the mvc part of the front-end. More specifically, I would like a Model interface that would abstract away the complexity of the offline and real-time aspects.

For instance, from the Angular side, I would like to subscribe to my Model and get notified when it changes. I would also like to have a .save() method that would handle the syncing with the server and other clients.

That library should:

  • Work offline: it will save the data in the local_storage and sync back with the server when it gets back online.

  • Listen to real-time changes, update its model and propagate the changes to the listeners.

  • Work well with a standard REST interface.

So, just as a quick pseudocode example, in Angular I would like to do:

my_controller = function($scope) {
  User.find_all(function(users) {
    $scope.users = users;
  });
}

User is the model abstraction.. when it gets a real-time update, my $scope.users should change accordingly.

$scope.users[0].set('name', 'testing)

This should save the model to the server. Or, if offline, should save it locally and sync it later on when it's back online.

I know there are online services trying to accomplish that, such as Firebase and kinvey. The problem with these tools is that it only offers a hosted solution. I need to controller the REST server and the database. So, basically, I'm looking for a "Firebase" library - without all the authentications and authorizations - that could work for with a REST server and pubsub third party.

Thanks!

this is a bit long for an answer, but i don't have it published yet.

function monitor(obj, callBack){


var api={
        patch: patchObjectWithDiff, 
        init: init, 
        resolve: resolve, 
        snapshot: snapshot, 
        diff: diff, 
        update: changeMonitor 
   };



function merge2(o, ob) {
     for (var z in ob) {
        if (ob.hasOwnProperty(z)) {
           if(typeof ob[z]=="object"){ 
                if(ob[z]==null){
                    delete o[z];
                }else{
                    merge2( o[z] || {},  ob[z]);
                }

            }else{
                 o[z] = ob[z];
            }

        }
    }
    return o;
}






function snapshot(obj) { 
    var out = [];
    function merge3(ob, path) {
        path = path || [];
                var tp;
        for(var z in ob) {
            if(ob.hasOwnProperty(z)) {
                if(ob[z] && typeof ob[z] == "object" && [Date, RegExp].indexOf(ob[z].constructor) == -1) {

                                        tp=path.concat(z);
                    out.push({
                                                path:  tp.join("`"),
                                                path2:  tp,
                                                dt:  "set",
                                                date:  +new Date,
                                                v: Array.isArray(ob[z]) ? "[]" : "{}"
                                        });

                    merge3(ob[z], path.concat(z));
                } else {
                                        tp=path.concat(z);
                    out.push({
                                                path:  tp.join("`"),
                                                path2:  tp,
                                                type:  "set",
                                                dt:  +new Date,
                                                v: JSON.stringify(ob[z]) 
                                        });
                }
            }
        }
    }

    merge3(obj);
    return out;
};



function diff(d1, d2){

  var out=d2.filter(function(a,b,c){
     var ov=JSON.stringify(a.v);
     return d1.some(function(aa,bb){ return aa.path==a.path && JSON.stringify(aa.v) != ov;  });
  }),

  // find deletions
  dels=d1.filter(function(a,b,c){
     return !d2.some(function(aa,bb){ if(aa.path==a.path ){  return true; };  });
  }),

  allPaths=dels.map(function(a){return a.path}).sort(),

  dels2=dels.filter(function eliminateUnneededSubBranches(a){

        var pos=allPaths.indexOf( a.path2.slice(0,-1).join("`") );

        return pos==-1 || pos >= allPaths.indexOf(a.path);

  }).map(function(a){a.type="del"; delete a.v; return a;});


  [].push.apply(out, dels2);


 //find inserts


var outNew=d2.filter(function(a,b,c){
     var ov=JSON.stringify(a.v);
     return !d1.some(function(aa,bb){ return aa.path==a.path  });
  });

 [].push.apply(out, outNew);



  return out.map(function(a){
       var x= {
         dt: a.dt,
         k: a.path2
       };

       if(a.hasOwnProperty("v")){ x.v=a.v; }

       return x;

            a.k=a.path2; 
            delete a.path; 
            delete a.path2; 
            delete a.type;
      return a;
  });
}



function resolve(path, object){
  var tob=object;
  path.map(function(a){ return (tob=tob[a])||tob; })
 return tob;
}








function patchObjectWithDiff(diff, object){

  diff.forEach(function(a,b,c){
       var p= resolve(a.k.slice(0,-1), object), 
           k= a.k.slice(-1)[0];

       if(a.hasOwnProperty("v")){ //set:
              p[k]=JSON.parse(a.v);
             if(String(p[k]).match(/Z$/)){ p[k]=new Date(''+p[k]) || p[k]; }
        }else{ // del:
           if(Array.isArray(p)){ p.splice(k,1); }else{ delete p[k]; }
       }
  });

   return object;
}











    var init=snapshot(JSON.parse(JSON.stringify(obj))),
          id=Math.random()+ Number(new Date());


    var init=snapshot(obj);

    function changeMonitor(){
        var thisTime=snapshot(obj),
               diffs=diff(init, thisTime);
        if(diffs.length){  
            api.diffs=diffs;
            (callBack||console.log.bind(console))("objectUpdate", diffs );
            init=thisTime;
        }//end if change?
    }

    setInterval(changeMonitor, 2500);

 return api;

}

demo / example usage:

var obj={a:1, b:[1,2,3], c: false}; // a model object
var dupe=JSON.parse(JSON.stringify(obj)); // a cheap clone of the data for demo use

//subscribe this object to updates    
var mon=monitor(obj, function(type, changes){console.log(type,  changes); });

// make some changes to the object:
obj.e="cool!";
obj.b.push(5);
obj.a=7;

// manually call update instead of waiting for the bundler:
//  (this is needed for this demo so we can reconcile the changes in sync and view the output)
mon.update();

// now apply stored changes to the clone of the orig data:
var updatedDupe= mon.patch(mon.diffs, dupe);

// use a cheap and easy but not production-reliable to compare the objects:
JSON.stringify(updatedDupe)==JSON.stringify(obj); // should be true

tested in chrome and firefox.

be aware that this particular demo's use of JSON depends on some luck, and consistent key ordering, which is not guaranteed by the JS spec. Key order doesn't really matter, but it might cause the JSON.stringify() == comparison to fail, even though the object's properties are indeed sync'd. This is just for demonstration's sake to get a true/false answer if it works, don't beat me up...

you can give it a custom callback to send("diff", {diffs:mon.diffs}) the changes as they happen and then use a subscribed event from pusher et al like on("diff", function(e){mon.patch(e.diffs, obj);}); to apply your changes and trigger the view update in your MVC.

I'll leave it to you to work localStorage and online/offline in there as you need, it should be really easy after getting this far.

All diffs in the change list come with three keys:

  {"dt":1392348959730,"k":["b","3"],"v":"5"}
dt: a timestamp of when the change was discovered
k: the key path where the change was detected
v: what the discovered changed value is as of dt

This script is hot off the press and i haven't had time to write proper documentation, but i figure it might help or at least inspire a solution that works for you.

I think you should start researching HTML5 WebSockets: http://www.websocket.org/

It allows bi-directional communication between server and client, client pull and server push.

Then look at SignalR, the asp.net implementation of HTML5 WebSockets: http://www.asp.net/signalr

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM