Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
A pure javascript implementation of the SyncML adapter framework and protocol.
JavaScript Other
branch: master

README.md

syncml-js

A pure javascript implementation of the SyncML adapter framework and protocol.

Status

The syncml-js package is in "beta". That means that everything is fully functional, however it has not had much real-world experience, and therefore there are definitely bugs hidden here and there. Use with caution, and always backup your data before doing anything (see sbt for a convenient backup tool that uses this SyncML implementation).

Installation

This is the easy part, provided you have npm installed:

npm install syncml-js

Note that syncml-js does not provide any command-line tools, so you typically would list it in your package.json's "dependencies" attribute and use it in your application.

Usage

For in-depth discussion on how to use a SyncML protocol library, please take a look at pysyncml: despite being in python, it uses the same concepts and almost an identical API. Basically, creating a SyncML client comprises the following steps:

  1. Create the actual data layer of where your data lives, how it is stored and formatted, and the mechanisms that users use to interact and manipulate it.

  2. Create an "Agent" class, which implements an API that forms the bridge between your data layer and the SyncML adapter.

  3. Instantiate a "Context" and an "Adapter", classes provided by this package, which are responsible for actually communicating with a SyncML peer.

  4. Ensure that changes made to the data layer are registered to the SyncML library.

  5. Synchronize the adapter, using the adapter.sync() method for client-side initialization or adapter.handleRequest() for server-side request handling.

Here is a quick example of such a client, which assumes that the first step, the local data layer, is implemented elsewhere. It uses the amdefine package to handle dependencies, underscore for general utilities, and cascade for taking some of the pain out of callback-hell:

if ( typeof(define) !== 'function' )
  var define = require('amdefine')(module);

define(['syncml-js', 'underscore', 'cascade'], function(syncml, _, cascade) {

  //---------------------------------------------------------------------------
  // define an agent class that is the bridge between your actual
  // data and the SyncML adapter protocol framework.

  var MyAgent = syncmljs.Agent.extend({

    constructor: function(options) {
      // TODO: constructor items
    },

    getContentTypes: function() {
      // TODO: returns the content-types that your agent supports
      return [
        new syncmljs.ContentTypeInfo('text/calendar', '2.0', {preferred: true}),
        new syncmljs.ContentTypeInfo('text/x-vcalendar', ['1.0', '1.1'])
      ];
    },

    dumpsItem: function(item, contentType, version, cb) {
      // TODO: serialize the item
      return cb(null, CONVERT-ITEM-TO-DATA [, CONTENT-TYPE [, VERSION ] ]);
    },

    loadsItem: function(data, contentType, version, cb) {
      // TODO: de-serialize an item
      var item = CONVERT-DATA-TO-ITEM;
      return cb(null, item);
    },

    getAllItems: function(cb) {
      // TODO: supplies a list of all existing items
      return cb(null, LIST);
    },

    addItem: function(item, cb) {
      // TODO: adds the new item to local storage -- it MUST also set
      //       the `id` attribute of the new item
      return cb(null, ITEM-WITH-NEW-ID);
    },

    getItem: function(itemID, cb) {
      // TODO: fetches the item with the specified `itemID`
      return cb(null, ITEM);
    },

    replaceItem: function(item, reportChanges, cb) {
      // TODO: updates the local storage with the new item, replacing the
      //       existing item with ID ``item.id``. if `reportChanges` is
      //       true (usually when this agent is acting as the server), the
      //       agent should provide a changeSpec string.
      return cb(null, CHANGESPEC);
    },

    deleteItem: function(itemID, cb) {
      // TODO: removes the item with the specified ID `itemID` from the
      //       local storage.
      return cb(null);
    }

  });

  //---------------------------------------------------------------------------
  // create a SyncML adapter, which will be the object that actually
  // communicates and synchronizes your data with other SyncML peers.
  // this example shows how to create a client-side peer -- see the
  // full documentation for how to create a server-side peer.

  var context = new syncml.Context({
    // `storage` must point to an "IndexedDB" implementation. if
    // operating within a modern browser context, you can use
    // the IndexedDB implementation provided by the browser:
    storage: window.indexedDB,
    // you can namespace this context's data with the `prefix`
    // parameter:
    prefix:  'my-syncml-client',
  });


  var exit = function(err, exitcode) {
    util.error('[**] ERROR: ' + err);
    process.exit(exitcode);
    return;
  }

  context.getEasyClientAdapter({
    displayName: 'My Remote Calendar Database',
    devInfo: {
      devID               : 'a-globally-unique-identifier',
      devType             : syncml.DEVTYPE_WORKSTATION,
      manufacturerName    : 'syncml-js',
      modelName           : 'syncml-js.example.client',
      hierarchicalSync    : false
    },
    stores: [
      {
        uri          : 'cal',
        displayName  : 'Remote Calendar',
        maxGuidSize  : 32,
        maxObjSize   : 2147483647,
        agent        : new MyAgent()
      }
    ],
    peer: {
      url      : 'https://example.com/sync',
      auth     : syncml.NAMESPACE_AUTH_BASIC,
      username : 'guest',
      password : 'guest'
    }
    // you can either specify how to bind local stores to the stores
    // on the server, or you can let syncml-js apply its best guess.
    // here, an example of how to bind the local "cal" datastore to
    // the server's "calendar" datastore:
    //
    //   routes: [
    //     [ 'cal', 'calendar' ]
    //   ]
  }, function(err, adapter, stores, peer) {
    if ( err )
      return exit(err, 20);

    // now the adapter is fully setup
    report_changes(adapter, stores[0], peer);

  });

  //---------------------------------------------------------------------------
  // with an adapter and peer setup, we can scan for local changes, report
  // them, and then start the synchronization. the "find_all_*" functions are
  // expected to be provided by your local data storage layer.

  var report_changes = function(adapter, store, peer) {

    find_all_add(function(items) {
      cascade(items, cascade.queue, function(item, cb) {
        store.registerChange(item.id, syncml.ITEM_ADDED, null, cb);

      }, function(err) {
        if ( err )
          return exit(err, 30);

        find_all_mod(function(items) {
          cascade(items, cascade.queue, function(item, cb) {
            store.registerChange(item.id, syncml.ITEM_MODIFIED, null, cb);

          }, function(err) {
            if ( err )
              return exit(err, 31);

            find_all_del(function(items) {
              cascade(items, cascade.queue, function(item, cb) {
                store.registerChange(item.id, syncml.ITEM_DELETED, null, cb);

              }, function(err) {
                if ( err )
                  return exit(err, 32);

                sync_peer(adapter, store, peer);

              });
            });
          });
        });
      });
    });
  };

  //---------------------------------------------------------------------------
  // now that all of the changes have been reported, we can actually do the
  // synchronization with a remote peer.

  var sync_peer = function(adapter, store, peer) {

    adapter.sync(peer, syncml.SYNCTYPE_AUTO, function(err, stats) {

      if ( err )
        return exit(err, 40);

      var stream = new syncml.Stream.extend({
        write: function(data) {
          util.print(data);
        }
      });

      syncml.describeStats(stats, stream, {
        title: 'Remote Calendar Sync Results'
      });

      process.exit(0);

    });
  };

});
Something went wrong with that request. Please try again.