// vim: ts=2 sw=2 expandtab cindent
//
// BEGIN FLOCK GPL
//
// Copyright Flock Inc. 2005-2008
// http://flock.com
//
// This file may be used under the terms of the
// GNU General Public License Version 2 or later (the "GPL"),
// http://www.gnu.org/licenses/gpl.html
//
// Software distributed under the License is distributed on an "AS IS" basis,
// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
// for the specific language governing rights and limitations under the
// License.
//
// END FLOCK GPL

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;

const WEBSVCMGR_CID = Components.ID("{23d157f0-5941-11db-b0de-0800200c9a66}");
const WEBSVCMGR_CONTRACTID = "@flock.com/webservice-manager;1";

//const OBS_TOPIC_FLOCKDATAREADY = "flock-data-ready";
const OBS_TOPIC_FLOCKDOCREADY = "FlockDocumentReady";
const OBS_TOPIC_FORMSUBMIT = "earlyformsubmit";
const OBS_TOPIC_XPCOMSHUTDOWN = "xpcom-shutdown";


// ========================================================
// ========== BEGIN flockWebServiceManager class ==========
// ========================================================

function flockWebServiceManager()
{
  this._logger = Cc["@flock.com/logger;1"].createInstance(Ci.flockILogger);
  this._logger.init("webServiceManager");
  this._logger.info("Created Web Service Manager Object");

  // Associative array where keys are service contract IDs and values are URNs
  this.mAllServicesByContract = [];

  // Associative array where keys are URNs and values are XPCOM services
  this.mInstantiatedServices = [];

  // Cache the .domains properties of each service, by URN
  this.mDomains = [];

  // mTempIgnore is used to keep track of which services the user has elected
  // NOT to enable Flock support for during this browser session
  this.mTempIgnore = [];

  // mListeners is the array of flockIWebServiceAccountListeners
  this.mListeners = [];
  
  // mKeepAccounts is an associative array of service URNs to dates, used by
  // autoKeepAccountForService()
  this.mKeepAccounts = [];

  this.obs = Cc["@mozilla.org/observer-service;1"]
               .getService(Ci.nsIObserverService);
  //this.obs.addObserver(this, OBS_TOPIC_FLOCKDATAREADY, false);
  this.obs.addObserver(this, OBS_TOPIC_XPCOMSHUTDOWN, false);
  // addObserver calls for FLOCKDOCREADY and FORMSUBMIT are in this.setup()

  this.mEnabled = true;
  this.mRegisteredEvents = false;

  this._metrics = Cc["@flock.com/metrics-service;1"]
                  .getService(Ci.flockIMetricsService);
  
  this.setup();
}


// BEGIN nsISupports interface
flockWebServiceManager.prototype.QueryInterface =
function flockWebServiceManager_QueryInterface(aIID)
{
  if ( !aIID.equals(Ci.nsISupports) &&
       !aIID.equals(Ci.nsIObserver) &&
       !aIID.equals(Ci.flockIWebServiceManager) )
  {
    throw Cr.NS_ERROR_NO_INTERFACE;
  }
  return this;
}
// END nsISupports interface


// BEGIN nsIObserver interface
flockWebServiceManager.prototype.observe =
function flockWebServiceManager_observe(aSubject, aTopic, aData)
{
  switch (aTopic) {
    //case OBS_TOPIC_FLOCKDATAREADY:
      //this.obs.removeObserver(this, OBS_TOPIC_FLOCKDATAREADY);
      //this.setup();
      //break;

    case OBS_TOPIC_FORMSUBMIT:
      this.onFormSubmit(aSubject, aData);
      break;

    case OBS_TOPIC_FLOCKDOCREADY:
      this.onDocumentReady(aSubject, aData);
      break;

    case OBS_TOPIC_XPCOMSHUTDOWN:
      if (this.mRegisteredEvents) {
        this.obs.removeObserver(this, OBS_TOPIC_FLOCKDOCREADY);
        this.obs.removeObserver(this, OBS_TOPIC_FORMSUBMIT);
      }
      this.obs.removeObserver(this, OBS_TOPIC_XPCOMSHUTDOWN);
      break;
  }
}
// END nsIObserver interface


// BEGIN flockIWebServiceManager interface
flockWebServiceManager.prototype.instantiateAll =
function flockWebServiceManager_instantiateAll()
{
  this._logger.info(".instantiateAll()");
  var catMgr = Cc["@mozilla.org/categorymanager;1"]
               .getService(Ci.nsICategoryManager);
  var svcEnum = catMgr.enumerateCategory("wsm-startup");
  while (svcEnum.hasMoreElements()) {
    var entry = svcEnum.getNext().QueryInterface(Ci.nsISupportsCString);
    if (!entry) {
      continue;
    }
    var contractID = catMgr.getCategoryEntry("wsm-startup", entry.data);
    this.instantiateWebService(contractID);
  }
}

flockWebServiceManager.prototype.registerWebService =
function flockWebServiceManager_registerWebService(aService)
{
  aService.QueryInterface(Ci.flockIManageableWebService);
  this._logger.info(".registerWebService(contract: "+aService.contractId+" URN: "+aService.urn+")");
  this.mAllServicesByContract[aService.contractId] = aService.urn;
  this.mInstantiatedServices[aService.urn] = aService;
  this.checkFirstRun(aService);
  this.checkPasswordsForService(aService);
}

flockWebServiceManager.prototype.unregisterWebService =
function flockWebServiceManager_unregsiterWebService(aService)
{
  aService.QueryInterface(Ci.flockIManageableWebService);
  this._logger.info(".unregisterWebService("+aService.urn+")");
  this.mInstantiatedServices[aService.urn] = undefined;
}

flockWebServiceManager.prototype.addListener = 
function flockWebServiceManager_addListener(aListener)
{
  aListener.QueryInterface(Ci.flockIWebServiceAccountListener);
  this.mListeners.push(aListener);
  this._logger.info(".addListener() ["+this.mListeners.length+"]");
}

flockWebServiceManager.prototype.removeListener = 
function flockWebServiceManager_removeListener(aListener)
{
  for (var i = 0; i < this.mListeners.length; ++i) {
    if (aListener == this.mListeners[i]) {
      this.mListeners.splice(i, 1);
      break;
    }
  }
}

flockWebServiceManager.prototype.autoKeepAccountForService =
function flockWebServiceManager_autoKeepAccountForService(aServiceURN)
{
  var stopTime = new Date();
  stopTime.setHours(stopTime.getHours() + 1);
  this.mKeepAccounts[aServiceURN] = stopTime.getTime();
}
// END flockIWebServiceManager interface


// BEGIN internal helper functions
flockWebServiceManager.prototype.setup =
function flockWebServiceManager_setup()
{
  this._logger.info(".setup()");

  this.coop = Cc["@flock.com/singleton;1"]
              .getService(Ci.flockISingleton)
              .getSingleton("chrome://flock/content/common/load-faves-coop.js")
              .wrappedJSObject;

  this.acUtils = Cc["@flock.com/account-utils;1"]
                   .getService(Ci.flockIAccountUtils);

  this._profiler = Cc["@flock.com/profiler;1"].getService(Ci.flockIProfiler);

  var prefSvc = Cc["@mozilla.org/preferences-service;1"]
                  .getService(Ci.nsIPrefService);
  var prefs = prefSvc.getBranch(null);
  this.firstRunPrefs = prefSvc.getBranch("flock.service.firstruncomplete.");
  this.svcConfPrefs = prefSvc.getBranch("flock.service.confirmation.");

  this.mEnabled = true;
  if (prefs.getPrefType("flock.service.webservicemanager.enabled")) {
    this.mEnabled = prefs.getBoolPref("flock.service.webservicemanager.enabled");
  }
  if (!this.mEnabled) { return; }

  this.obs.addObserver(this, OBS_TOPIC_FORMSUBMIT, false);
  this.obs.addObserver(this, OBS_TOPIC_FLOCKDOCREADY, false);
  this.mRegisteredEvents = true;

  // Web Service Manager is responsible for determining when to instantiate any
  // services in the "wsm-startup" category.
  var catMgr = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
  var svcEnum = catMgr.enumerateCategory("wsm-startup");
  var firstRun = ( !this.firstRunPrefs.getPrefType("webservicemanager") ||
                   !this.firstRunPrefs.getBoolPref("webservicemanager") );
  this._logger.info((firstRun) ? "First run" : "NOT first run");
  while (svcEnum.hasMoreElements()) {
    var entry = svcEnum.getNext().QueryInterface(Ci.nsISupportsCString);
    if (!entry) continue;
    var contractID = catMgr.getCategoryEntry("wsm-startup", entry.data);
    this._logger.info("checking "+contractID);
    // Always instantiate web services components on first run, or when an
    // account has already been configured
    var shouldInstantiate = ( firstRun ||
      this.coop.Account.find({serviceId: contractID}).length );
    var findSvc;
    if (!shouldInstantiate) {
      // If it doesn't exist in the RDF, then it was not properly initialized
      // last time, so make sure it gets instantiated now
      findSvc = this.coop.Service.find({serviceId: contractID});
      if (findSvc.length == 0) { shouldInstantiate = true; }
    }
    if (shouldInstantiate) {
      var service = this.instantiateWebService(contractID);
      this.checkFirstRun(service);
    } else {
      // There's still a possibility that this is the first run for this
      // service -- eg. if it's from an extention
      this._logger.info("Decided NOT to instantiate "+contractID);
      this.mAllServicesByContract[contractID] = findSvc[0].id();
      this.checkFirstRun(null, contractID);
    }
  }
  this.firstRunPrefs.setBoolPref("webservicemanager", true);
}

/**
 * Every web service component must get instantiated at least once - generally
 * on first-run - so that it can update the RDF, etc.
 */
flockWebServiceManager.prototype.checkFirstRun =
function flcokWebServiceManager_checkFirstRun(aService, aServiceContractID)
{
  var serviceURN = (aService) ? aService.urn
    : this.coop.Service.find({serviceId: aServiceContractID})[0].id();

  this._logger.info(".checkFirstRun() "+serviceURN);
  if ( !this.firstRunPrefs.getPrefType(serviceURN) ||
       !this.firstRunPrefs.getBoolPref(serviceURN) )
  {
    // This is the first time we are running with this service, so ensure
    // that it is instantiated
    if (!aService) {
      aService = this.instantiateWebService(aServiceContractID);
    }
    this.firstRunPrefs.setBoolPref(serviceURN, true);
  }
}

flockWebServiceManager.prototype.instantiateWebService =
function flockWebServiceManager_instantiateWebService(aServiceContractID)
{
  this._logger.info(".instantiateWebService('"+aServiceContractID+"')");
  var service = Cc[aServiceContractID]
    .getService(Ci.flockIManageableWebService);
  this.mAllServicesByContract[aServiceContractID] = service.urn;
  this.mInstantiatedServices[service.urn] = service;
  this.checkPasswordsForService(service);
  return service;
}

flockWebServiceManager.prototype.checkPasswordsForService =
function flockWebServiceManager_checkPasswordsForService(aService)
{
  if (aService.needPassword) {
    // This service requires that we have a password stored in order to have
    // a properly configured account.  Check if we have accounts, and if they
    // don't have passwords then log them out in order to force the user to
    // log in again.
    var c_accounts = this.coop.Account.find({serviceId: aService.contractId});
    var missingAPassword = false;
    for (var i = 0; i < c_accounts.length; i++) {
      var c_acct = c_accounts[i];
      var pw = this.acUtils.getPassword(aService.urn+":"+c_acct.accountId);
      if (!pw) {
        missingAPassword = true;
        if (c_acct.isAuthenticated) {
          aService.logout();
          this.acUtils.markAllAccountsAsLoggedOut(aService.contractId);
          return;
        }
      }
    }
    if (missingAPassword) {
      // We are missing a password, but we don't know whether that account is
      // currently logged in or not.  If it's an account that the user never
      // uses, then we don't want to force logout here (because we could be
      // logging them out of the account they actually DO use)
      if (c_accounts.length == 1) {
        // But in this case there's only one account.  This must be the one
        // we're missing the password for.  So we want to log the service out,
        // regardless.  This will force the user to log in again, so we can
        // re-capture that password.
        aService.logout();
        this.acUtils.markAllAccountsAsLoggedOut(aService.contractId);
      }
    }
  }
}

flockWebServiceManager.prototype.onFormSubmit =
function flockWebServiceManager_onFormSubmit(aForm, aData)
{
  this._logger.info(".onFormSubmit('"+aData+"')");
  var url = aForm.ownerDocument.QueryInterface(Ci.nsIDOMHTMLDocument).URL;
  if (!this.mIOS) {
    this.mIOS = Cc["@mozilla.org/network/io-service;1"]
                  .getService(Ci.nsIIOService);
  }
  var uri = this.mIOS.newURI(url, null, null);

  for (var contractID in this.mAllServicesByContract) {
    this._logger.info("try "+contractID);

    // Check the domain
    var serviceURN = this.mAllServicesByContract[contractID];
    this._logger.info(" - URN is "+serviceURN);
    if (!this.testDomains(serviceURN, uri.host)) {
      this._logger.info("NO MATCH on domains for "+serviceURN);
      continue;
    }
    this._logger.info("form domains MATCH for "+serviceURN);

    // The domain matched, so ensure this service is instantiated
    var service = this.mInstantiatedServices[serviceURN];
    if (!service) {
      service = this.instantiateWebService(contractID);
    }

    // Ask the service if it owns this form being submitted
    if (!service.ownsLoginForm(aForm)) { continue; }
    this._logger.info(" service '"+service.urn+"' says it owns this form.");

    var pw = service.getCredentialsFromForm(aForm);
    if (pw) {
      // The user has just tried to log in, sign up for, or change their
      // password on this service.  We don't know which account it was for,
      // or if they were successful yet -- so for now, store the password
      // temporarily, associated with the service URN.
      this._logger.info(" saving TEMPORARY password for '"+service.urn+"'");
      var origin = pw.QueryInterface(Ci.flockIPassword);
      this.acUtils.setTempPassword(service.urn, pw.user, pw.password, origin.formType);
    } else {
      this._logger.info(" service'"+service.urn+"' could not retrieve login info, nothing we can do :(");
    }
    // This form has already been claimed by a service, don't give the other
    // services a chance to claim it.
    break;
  }
}

flockWebServiceManager.prototype.onDocumentReady =
function flockWebServiceManager_onDocumentReady(aDocument, aURL)
{
  var profilerEvt = this._profiler.profileEventStart("onDocumentReady");
  this._logger.info(".onDocumentReady('"+aURL+"')");

  // flockIManageableWebServices only know how to deal with HTML documents
  if (!(aDocument instanceof Ci.nsIDOMHTMLDocument)) {
    this._profiler.profileEventEnd(profilerEvt, aURL);
    return;
  }

  if (!this.mIOS) {
    this.mIOS = Cc["@mozilla.org/network/io-service;1"]
                  .getService(Ci.nsIIOService);
  }
  var uri = this.mIOS.newURI(aURL, null, null);
  var isOwned = false;

  // Function to alert user if there's a need to re-login
  var inst = this;
  var notifyPasswordMissing = function wsm_passwordMissing(aService) {
    for (var i = 0; i < inst.mListeners.length; ++i) {
      if (inst.mListeners[i].onAccountPasswordMissing) {
        inst.mListeners[i].onAccountPasswordMissing(aService, aDocument);
      }
    }
  };

  // Loop through all the services
  for (var contractID in this.mAllServicesByContract) {
    this._logger.info("try "+contractID);

    // Check the domain
    var serviceURN = this.mAllServicesByContract[contractID];
    this._logger.info(" - URN is "+serviceURN);
    if (!this.testDomains(serviceURN, uri.host)) { continue; }
    this._logger.info("domains MATCH for "+serviceURN);

    // The domain matched, so ensure this service is instantiated
    var service = this.mInstantiatedServices[serviceURN];
    if (!service) {
      service = this.instantiateWebService(contractID);
    }

    // Ask the service if it actually cares about this document
    if (!service.ownsDocument(aDocument)) { continue; }
    this._logger.info(" service '"+service.urn+"' says IT OWNS this document.");
    isOwned = true;

    // We now know the service cares about this document, so next check if
    // it's a login landing page
    if (service.docRepresentsSuccessfulLogin(aDocument)) {
      this._logger.info(" service '"+service.urn+"' says this page represents a successful login!");

      // User has just logged in -- see which account
      var acct_id = service.getAccountIDFromDocument(aDocument);

      if (acct_id) {
        if (this.getServiceConfirmation(service.urn)) {
          // We have detected an account ID on this page, and furthermore the
          // service confirmation has not forbade us from trying to use this
          // service.  So lets's see if we need to create an account.

          // If we have a temp password stored for this service (from the
          // onFormSubmit call), then we now know that we can associate it with
          // this account ID
          var pw = this.acUtils.getTempPassword(service.urn);
          if (pw) {
            var origin = pw.QueryInterface(Ci.flockIPassword);
            if (origin.formType == "signup") {
              this._metrics.report("Account-Signup", service.shortName);
            }

            this.acUtils.setTempPassword(service.urn + ":" + acct_id, pw.user,
                                         pw.password, origin.formType);
            this.acUtils.clearTempPassword(service.urn);
          }

          // The service has told us the account ID represented by the
          // document, but that account might not actually exist in the RDF.
          var acctURN = this.acUtils.getAccountURNById(service.urn, acct_id);

          if (!acctURN) {
            // Flock does not yet know about this account
            if (pw || !service.needPassword) {
              // We have a password, or else this service does not require a
              // password in order for us to configure an account.
              this._logger.info(" set up new temporary account: " + acct_id);
              this.setUpAccount(service, acct_id, aDocument);
            } else {
              // We don't have a stored password. That may be due to migrated
              // cookies (bug 6404).
              this._logger.info(" no password associated to the account");
              notifyPasswordMissing(service);
            }
          } else if (this.acUtils.isTransient(acctURN)) {
            // The account is set up, but transient.  We should give the user
            // the option to keep it, unless they have already explicitly
            // dismissed that notification.
            this._logger.info(" found existing temp account: " + acct_id);
            this.checkNotifyAccount(aDocument, service.getAccount(acctURN));
          } else {
            // The account is already set up and is non-transient.  The only
            // thing to do is make sure we don't have an out-of-date password.
            this._logger.info(" found existing account: "+acct_id);
            if (pw) {
              this.acUtils.makeTempPasswordPermanent(service.urn+":"+acct_id);
            }
          }
        } else {
          this._logger.info(" user indicated to ignore this service");
        }
      } else {
        this._logger.info(" unable to find account ID on this page");
        // This is a somewhat broken state.  We know we're logged in, but we
        // can't tell which account.  Our only hope is to make the user log
        // out and log in again.  Using the "no password" notification to do
        // that.
        notifyPasswordMissing(service);
      }
    }

    // Give the service a chance to update the auth state of its account(s)
    service.updateAccountStatusFromDocument(aDocument);

    // Give media services a chance to detect media
    var isMediaService = true;
    try {
      service.QueryInterface(Ci.flockIMediaWebService);
    } catch (ex) {
      isMediaService = false;
    }
    if (isMediaService) {
      service.decorateForMedia(aDocument);
    }
    this._profiler.profileEventEnd(profilerEvt, aURL);
    return;
  }
  // If no service owns the doc, notify explore button menu to redraw
  if (!isOwned) {
    this.obs.notifyObservers(aDocument, "media", "media:update");
  }
  this._profiler.profileEventEnd(profilerEvt, aURL);
}

flockWebServiceManager.prototype.testDomains =
function flockWebServiceManager_testDomains(aServiceURN, aHost)
{
  var domains = this.mDomains;
  if (!domains[aServiceURN]) {
    var domainList = this.coop.get(aServiceURN).domains;
    var domainArr = [];
    if (domainList) {
      this._logger.info("got domains for '"+aServiceURN+"': "+domainList);
      domains[aServiceURN] = domainList.split(",");
    }
  }
  var domainArr = domains[aServiceURN];
  if (!domainArr || domainArr.length == 0) {
    return true;
  }
  for (var i = 0; i < domainArr.length; i++) {
    var domain = domainArr[i];
    if (aHost == domain) return true;
    var idx = aHost.indexOf("."+domain);
    if ((idx > 0) && ((idx + domain.length + 1) == aHost.length)) return true;
  }
  this._logger.info("domains did not match for "+aServiceURN);
  return false;
}

flockWebServiceManager.prototype.getServiceConfirmation =
function flockWebServiceManager_getServiceConfirmation(aURN)
{
  if (this.mTempIgnore[aURN]) {
    return false;
  }
  var askUser = true;  // default value is to ask the user
  if (this.svcConfPrefs.getPrefType(aURN)) {
    askUser = this.svcConfPrefs.getBoolPref(aURN);
  }
  return askUser;
}

flockWebServiceManager.prototype.setServiceConfirmation =
function flockWebServiceManager_setServiceConfirmation(aUrn, aValue)
{
  this.svcConfPrefs.setBoolPref(aUrn, aValue);
}

flockWebServiceManager.prototype.setUpAccount =
function flockWebServiceManager_setUpAccount(aService, aNewAccountID, aDocument)
{
  this._logger.info("setUpAccount('"+aService.shortName+"', '"+aNewAccountID+"')");
  var inst = this;

  var authListener = {
    onSuccess: function (aAccount, aTopic) {
      inst.notifyListeners(aAccount, "accountAuthorized");
    },
    onError: function (aSubject, aTopic, aError) {
      inst._logger.debug("setUpAccount() authListener.onError()");
    }
  };

  var creationListener = {
    onSuccess: function (aAccount, aTopic) {
      aAccount.QueryInterface(Ci.flockIWebServiceAccount);
      inst.checkNotifyAccount(aDocument, aAccount);
      aAccount.activate(authListener);
    },
    onError: function (aSubject, aTopic, aError) {
      inst._logger.debug("setUpAccount() creationListener.onError()");
    }
  };

  // Accounts are created as 'transient' by default, unless
  // autoKeepAccountForService() has been called recently
  var setTransient = true;
  var stopTime = this.mKeepAccounts[aService.urn];
  if (stopTime) {
    var nowTime = (new Date()).getTime();
    if (nowTime < stopTime) {
      setTransient = false;
    } else {
      this.mKeepAccounts[aService.urn] = undefined;
    }
  }

  var acct = aService.addAccountById(aNewAccountID, setTransient, creationListener);
  this._metrics.report("Account-Add", aService.shortName);
  if (!setTransient) {
    acct.keep();
  }
  this._logger.info("acct is: "+acct);
}

flockWebServiceManager.prototype.checkNotifyAccount =
function flockWebServiceManager_checkNotifyAccount(aDocument, aAccount)
{
  aDocument.QueryInterface(Ci.nsIDOMDocument);
  aAccount.QueryInterface(Ci.flockIWebServiceAccount);
  this._logger.info("checkNotifyAccount('"+aAccount.urn+"')");
  this.notifyListeners(aAccount, "accountCreated", aDocument);
}

flockWebServiceManager.prototype.notifyListeners =
function flockWebServiceManager_notifyListeners(aAccount, aEventName, aDocument)
{
  switch (aEventName) {
    case "accountCreated":
      for (var i = 0; i < this.mListeners.length; ++i) {
        this.mListeners[i].onAccountCreated(aAccount, aDocument);
      }
      break;
    case "accountPasswordMissing":
      for (var i = 0; i < this.mListeners.length; ++i) {
        if (this.mListeners[i].onAccountPasswordMissing)
          this.mListeners[i].onAccountPasswordMissing(null, aDocument);
      }
      break;
    case "accountAuthorized":
      for (var i = 0; i < this.mListeners.length; ++i) {
        this.mListeners[i].onAccountAuthChange(aAccount);
      }
      break;
    default:
      break;
  }
}
// ========== END flockWebServiceManager class ==========



// ========== BEGIN XPCOM Module support ==========

// BEGIN flockWebServiceManagerModule object
var flockWebServiceManagerModule = {};

flockWebServiceManagerModule.registerSelf =
function flockWebServiceManagerModule_registerSelf(compMgr, fileSpec, location, type)
{
  compMgr = compMgr.QueryInterface(Ci.nsIComponentRegistrar);
  compMgr.registerFactoryLocation( WEBSVCMGR_CID, 
                                   "Flock Web Service Manager JS Component",
                                   WEBSVCMGR_CONTRACTID, 
                                   fileSpec, 
                                   location,
                                   type );
}

flockWebServiceManagerModule.getClassObject =
function flockWebServiceManagerModule_getClassObject(compMgr, cid, iid)
{
  if (!cid.equals(WEBSVCMGR_CID))
    throw Cr.NS_ERROR_NO_INTERFACE;
  if (!iid.equals(Ci.nsIFactory))
    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  return flockWebServiceManagerFactory;
}

flockWebServiceManagerModule.canUnload =
function flockWebServiceManagerModule_canUnload(compMgr)
{
  return true;
}
// END flockWebServiceManagerModule object


// BEGIN flockWebServiceManagerFactory object
var flockWebServiceManagerFactory = new Object();

flockWebServiceManagerFactory.createInstance =
function flockWebServiceManagerFactory_createInstance(outer, iid)
{
  if (outer != null) {
    throw Cr.NS_ERROR_NO_AGGREGATION;
  }
  return (new flockWebServiceManager()).QueryInterface(iid);
}
// END flockWebServiceManagerFactory object


// NS module entry point
function NSGetModule(compMgr, fileSpec) {
  return flockWebServiceManagerModule;
}

// ========== END XPCOM module support ==========
