Skip to content
This repository

No Plan Survives Battle

Designing an interface before you have consumers isn't easy, and I've almost certainly overlooked things or gotten a few things wrong. But I think it's better to go in with a small kernel of a plan.

Anyhow, this is my disclaimer that these interfaces might change over time. But it's a start.

Promises

Asynchronous functions should return a Promise, using toolkit's Promises library.

Connector Class

To be implemented in order to connect to contact providers. Some of this will be boilerplace that can likely be implemented in a base class for subclassing.

class {
  /**
   * Constructor
   * Called by the Connector Manager when its time to create a new instance of this class.
   * Unless you're testing, you probably shouldn't be instantiating one of these on your own -
   * you should let the Connector Manager take care of that.
   *
   * @param aAccountKey the unique accountKey assigned to this instance. Maps to the preferences
   *                    branch being used to store preferences.
   * @param aListener   the object that should be called when items are imported, added, removed, or
   *                    changed in this contact service.
   * @param aCache      a simple storage object that can be used to store a snap-shot of the contact
   *                    service's records. This is useful in order to determine if a contact has changed,
   *                    is new, or has been removed. This value is not passed if isSyncable is false.
   */
  (constructor): function(aAccountKey, aListener, aCache [optional]);

  /** Read-only attributes **/

  /**
   * Returns the unique accountKey passed into the constructor.
   */
  get accountKey;

  /**
   * Returns whether or not we should be reading in changes from this contact provider.
   */
  get isSyncable;

  /**
   * Returns whether or not we're able to write changes to this contact provider.
   */
  get isWritable;

  /**
   * Returns whether or not we need to poll the contact provider for changes, or if we can
   * expect the contact provider to push them on their own.
   */
  get shouldPoll;

  /**
   * Returns the human-readable and user-set name for this connection.  Alias for the
   * displayName preference.
   */
  get displayName;

  /** Writable attributes **/

  /**
   * Returns the JSON object representing the preferences for this Connector.
   */
  get prefs();

  /**
   * Writes a JSON object to the preferences for this Connector.
   *
   * @param aJSONObj the JSON object to write.
   */
  set prefs(aJSONObj);

  /**
   * Returns true if the init function of this conncetor has finished its job.
   */
  get initialized();

  /**
   * Returns true if the init function of this connector is in the process of finishing its job.
   */
  get initializing();

  /** Instance functions **/

  /**
   * Asynchronous function that tests that we can successfully connect to the
   * contact provider.
   *
   * @returns a Promise.                  
   */
  testConnection: function();

  /**
   * This function will spawn UI to ensure that the user has the right credentials
   * to connect to the contact provider.
   *
   * @returns a Promise.
   */
  authorize: function();

  /**
   * Called sometime after construction in order to let the connector do any
   * start-up work. For some connectors, this might mean populating the cache
   * if this is the first time being initted. This function will only be called
   * if a connection has been successfully established.
   *
   * @returns a Promise.
   */
  init: function();

  /**
   * Called to import records from this contact service.
   *
   * @returns a Promise.
   */
  read: function();

  /**
   * If the contact provider must be polled for updates, this function will be called
   * periodically by Ensemble to perform the polling. Incoming changes should be sent
   * through the listener defined in the constructor.
   *
   * @returns a Promise.
   */
  poll: function();

  /** Statics **/

  /**
   * Returns whether or not more than one instance of this class can be created.
   */
  static attribute boolean isSingleton;

  /**
   * Returns a chrome:// URL for the icon that represents this Connector.
   */
  static attribute string iconURL;

  /**
   * Returns a human-readable name for the Connector class.
   * Example: "Facebook Connector"
   */
  static attribute string serviceName;

  /**
   * Returns a chrome:// URL for the UI for creating the initial connection for an instance.
   */
  static attribute string createConnectionURI;

  /**
   * Returns a chrome:// URL for the UI for managing the connection for an instance.
   */
  static attribute string managementURI;

  /**
   * Returns a JSON object for the default preferences that an instance should inherit.
   */
  static attribute object defaultPrefs;

  /**
   * Returns a unique ID for this Connector class.
   */
  static attribute string uniqueID;
}

Record Class

A Backbone model that represents a record from a contact provider. Along with the usual Backbone functions, contains the following functions:

class Record {
  attribute id;
  Diff diff(aRecord);
  applyDiff(aDiff);
  merge(aRecord);
}

Listener Object

The listener object is called when items in a connector are imported, added, removed, or changed. The connector is responsible for maintaining a reference to the listener passed to it, and calling the correct functions when items are imported, added, removed or changed.

aListener = {

  /**
   * Called when a contact is *first* read in by the connector. This is not called when a contact is
   * added on the server side, but only when the connector does a first-read of the service's contacts.
   * It's called once per imported contact.
   *
   * @returns a Promise that resolves when the import work is done.
   */ 
  onImport: function(aRecord);

  onAdded: function(aRecord);

  onChanged: function(aId, aDiff);

  onRemoved: function(aId);
}

History

  • First draft published - October 22nd, 2012
  • Changes for caching - July 21, 2013
Something went wrong with that request. Please try again.