//Given data from troposphere, add it into the local store

return self.db.runInTransaction(function (txn) {

keep keep keep keep replace keep keep keep keep

insertData: function(serverData, syncComplete) {

var fs = require('fs');
var path = require('path');
var Future = require('fibers/future');
var _ = require('underscore');
var auth = require('./auth.js');
var config = require('./config.js');
var httpHelpers = require('./http-helpers.js');
var release = require('./release.js');
var files = require('./files.js');
var ServiceConnection = require('./service-connection.js');
var utils = require('./utils.js');
var buildmessage = require('./buildmessage.js');
var compiler = require('./compiler.js');
var uniload = require('./uniload.js');
var Console = require('./console.js').Console;

// Use uniload to load the packages that we need to open a connection to the
// current package server and use minimongo in memory. We need the following
// packages.
//
// meteor: base package and prerequsite for all others.
// ddp: DDP client interface to make a connection to the package server.
var getLoadedPackages = function () {
  return uniload.load({
    packages: [ 'meteor', 'ddp']
  });
};

// Opens a DDP connection to a package server. Loads the packages needed for a
// DDP connection, then calls DDP connect to the package server URL in config,
// using a current user-agent header composed by http-helpers.js.
var openPackageServerConnection = function (packageServerUrl) {
  return new ServiceConnection(
    getLoadedPackages(),
    packageServerUrl || config.getPackageServerUrl(),
    {headers: {"User-Agent": httpHelpers.getUserAgent()},
     _dontPrintErrors: true});
};

var emptyCachedServerDataJson = function () {
  return {
    syncToken: { format: "1.1" },
    collections: null
  };
};

// Given a connection, makes a call to the package server.  (Checks to see if
// the connection is connected, and reconnects if needed -- a workaround for
// the fact that connections in the tool do not reconnect)
exports.callPackageServer = function (conn) {
  if (!conn.connected) {
    conn.close();
    conn = exports.loggedInPackagesConnection();
  }
  var args = _.values(arguments)
    .slice(1, arguments.length);
  return conn.call.apply(conn, args);
};

// Load the package data that was saved in the local data.json
// collection from the last time we did a sync to the server. Takes an
// optional `packageStorageFile` argument (defaults to
// `config.getPackageStorage()`). This return object consists of
//
//  - collections: an object keyed by the name of server collections, with the
//    records as an array of javascript objects.
//  - syncToken: a syncToken object representing the last time that we talked to
//    the server, to pass into the getRemotePackageData to get the latest
//    updates.
// If there is no data.json file, or the file cannot be parsed, return null for
// the collections and a default syncToken to ask the server for all the data
// from the beginning of time.
exports.loadCachedServerData = function (packageStorageFile) {
  var noDataToken = emptyCachedServerDataJson();

  packageStorageFile = packageStorageFile || config.getPackageStorage();

  try {
    var data = fs.readFileSync(packageStorageFile, 'utf8');
  } catch (e) {
    if (e.code == 'ENOENT') {
      return noDataToken;
    }
    // XXX we should probably return an error to the caller here to
    // figure out how to handle it
    Console.stderr.write("ERROR " + e.message + "\n");
    process.exit(1);
  }
  var ret = noDataToken;
  try {
    ret = JSON.parse(data);
  } catch (err) {
    // XXX error handling
    Console.stderr.write(
      "ERROR: Could not parse JSON for local package-metadata cache. \n");
    // This should only happen if you decided to manually edit this or
    // whatever. Regardless, go on and treat this as an empty file.
  }
  return ret;
};

// Requests and returns one page of new package data that we haven't cached on
// disk. We assume that data is cached chronologically, so essentially, we are
// asking for a diff from the last time that we did this.
// Takes in:
// - conn: the connection to use (does not have to be logged in)
// - syncToken: a syncToken object to be sent to the server that
//   represents the last time that we talked to the server.
// - _optionsForTest:
//    - useShortPages (Boolean). Ask the server for pages of ~3 records
//      instead of ~100, for testing pagination.
//
// Returns an object, containing the following fields:
//  - syncToken: a new syncToken object, that we can pass to the server in the future.
//  - collections: an object keyed by the name of server collections, with the
//    records as an array of javascript objects.
var loadRemotePackageData = function (conn, syncToken, _optionsForTest) {
  _optionsForTest = _optionsForTest || {};

  var syncOpts;
  if (_optionsForTest && _optionsForTest.useShortPages) {
    syncOpts = { shortPagesForTest: _optionsForTest.useShortPages };
  }
  var collectionData;
  if (syncOpts) {
    collectionData = exports.callPackageServer(conn,
        'syncNewPackageData', syncToken, syncOpts);
  } else {
    collectionData = exports.callPackageServer(conn,
        'syncNewPackageData', syncToken);
  }
  return collectionData;
};

// Contacts the package server to get the latest diff and writes changes to
// disk.
//
// Takes in cachedServerData, which is the processed contents of data.json. Uses
// those to talk to the server and get the latest updates. Applies the diff from
// the server to the in-memory version of the on-disk data, then writes the new
// file to disk as the new data.json.
//
// Returns null if contacting the server times out. Otherwise, returns
// all the data.
//
// options can include:
//  - packageStorageFile: String. The file to write the data to (overrides
//    `config.getPackageStorage()`)
//  - packageServerUrl: String. The package server (overrides
//    `config.getPackageServerUrl()`)
//  - useShortPages: Boolean. Request short pages of ~3 records from the
//    server, instead of ~100 that it would send otherwise
exports.updateServerPackageData = function (dataStore, options) {
  var results;
  buildmessage.capture({ title: 'Updating package catalog' }, function () {
    results = _updateServerPackageData(dataStore, options);
  });
  return results;
};

_updateServerPackageData = function (dataStore, options) {
  var self = this;
  options = options || {};
  if (dataStore === null)
    throw Error("Data store expected");

  var done = false;
  var ret = {resetData: false};

  var start = undefined;
  var state = { current: 0, end: 10, done: false};
  buildmessage.reportProgress(state);

  try {
    var conn = openPackageServerConnection(options.packageServerUrl);
  } catch (err) {
    self.handlePackageServerConnectionError(err);
    ret.data = null;
    return ret;
  }

  // Provide some progress indication for connection
  // XXX though it is just a hack
  state.current = 1;
  buildmessage.reportProgress(state);

  var getSomeData = function () {
    var syncToken = dataStore.getSyncToken() || {};

    if (!start) {
      start = syncToken.packages;
      state.end = Date.now() - start;
    }
    // XXX: Is packages the best progress indicator?
    state.current = syncToken.packages - start;
    buildmessage.reportProgress(state);

    var remoteData;
    try {
      remoteData = loadRemotePackageData(conn, syncToken, {
        useShortPages: options.useShortPages
      });
    } catch (err) {
      exports.handlePackageServerConnectionError(err);
      if (err.errorType === "DDP.ConnectionError") {
        done = true;
        return;
      } else {
        throw err;
      }
    }

    // Is the remote server telling us to ignore everything we've heard before?
    // OK, we can do that.
    if (remoteData.resetData) {
      dataStore.reset();
      // The caller may want to take this as a cue to delete packages from the
      // tropohouse.
      ret.resetData = true;
    }

    // We always write to the data store; the fact there is no data is itself data!
    // e.g. the last-refresh timestamp
    dataStore.insertData(remoteData);

    // If there is no new data from the server, don't bother writing things to
    // disk (unless we were just told to reset everything).
    if (!remoteData.resetData && _.isEqual(remoteData.collections, {})) {
      done = true;
      ret.data = {};
      return;
    }

    if (remoteData.upToDate)
      done = true;
  };

  try {
    while (!done) {
      getSomeData();
    }
  } finally {
    conn.close();
  }

  state.done = true;
  buildmessage.reportProgress(state);

  return ret;
};

var AlreadyPrintedMessageError = function () {};

// Returns a logged-in DDP connection to the package server, or null if
// we cannot log in. If an error unrelated to login occurs
// (e.g. connection to package server times out), then it will be
// thrown.
exports.loggedInPackagesConnection = function () {
  // Make sure that we are logged in with Meteor Accounts so that we can
  // do an OAuth flow.

  if (auth.maybePrintRegistrationLink({onlyAllowIfRegistered: true})) {
    // Oops, we're logged in but with a deferred-registration account.
    // Message has already been printed.
    throw new AlreadyPrintedMessageError;
  }

  if (! auth.isLoggedIn()) {
    // XXX we should have a better account signup page.
    Console.stderr.write(
"Please log in with your Meteor developer account. If you don't have one,\n" +
"you can quickly create one at www.meteor.com.\n");
    auth.doUsernamePasswordLogin({ retry: true });
  }

  var conn = openPackageServerConnection();

  var accountsConfiguration = auth.getAccountsConfiguration(conn);

  try {
    auth.loginWithTokenOrOAuth(
      conn,
      accountsConfiguration,
      config.getPackageServerUrl(),
      config.getPackageServerDomain(),
      "package-server"
    );
  } catch (err) {
    if (err.message === "access-denied") {
      // Maybe we thought we were logged in, but our token had been
      // revoked.
      Console.stderr.write(
"It looks like you have been logged out! Please log in with your Meteor\n" +
"developer account. If you don't have one, you can quickly create one\n" +
"at www.meteor.com.\n");
      auth.doUsernamePasswordLogin({ retry: true });
      auth.loginWithTokenOrOAuth(
        conn,
        accountsConfiguration,
        config.getPackageServerUrl(),
        config.getPackageServerDomain(),
        "package-server"
      );
    } else {
      throw err;
    }
  }
  return conn;
};

// XXX this is missing a few things:
//    - locking down build-time dependencies: tools version, versions
//      of all (not-built-from-source) plugins used
// in general, we need to include all the stuff that goes into the watchSet
// We include npm-shrinkwrap which does not go in the watchSet but
// probably should.
//
// In retrospect a better approach here might be to actually make "save source
// somewhere else" or perhaps "add source to tarball" be part of the package
// build itself...
var bundleSource = function (unipackage, includeSources, packageDir) {
  var name = unipackage.name;

  var tempDir = files.mkdtemp('build-source-package-');
  var packageTarName = name + '-' + unipackage.version + '-source';
  var dirToTar = path.join(tempDir, 'source', packageTarName);
  var sourcePackageDir = path.join(
    dirToTar,
    name
  );
  if (! files.mkdir_p(sourcePackageDir)) {
    Console.stderr.write('Failed to create temporary source directory: ' +
                         sourcePackageDir);
    return null;
  }

  includeSources.push('package.js');
  if (fs.existsSync(path.join(packageDir, '.npm/package/npm-shrinkwrap.json'))) {
    includeSources.push('.npm/package/npm-shrinkwrap.json');
  }
  _.each(unipackage.plugins, function (plugin, pluginName) {
    var pluginShrinkwrap = path.join('.npm/plugin/', pluginName,
                                     'npm-shrinkwrap.json');
    if (fs.existsSync(path.join(packageDir, pluginShrinkwrap))) {
      includeSources.push(pluginShrinkwrap);
    }
  });

  // We copy source files into a temp directory and then tar up the temp
  // directory. It would be great if we could avoid the copy, but as far
  // as we can tell, this is the only way to get a tarball with the
  // directory structure that we want (<package name>-<version-source/
  // at the top level).
  _.each(includeSources, function (f) {
    files.copyFile(path.join(packageDir, f),
                   path.join(sourcePackageDir, f));
  });

  // We put this inside the temp dir because mkdtemp makes sure that the
  // temp dir gets cleaned up on process exit, so we don't have to worry
  // about cleaning up our tarball (or our copied source files)
  // ourselves.
  var sourceTarball = path.join(tempDir, packageTarName + '.tgz');
  files.createTarball(dirToTar, sourceTarball);

  var tarballHash = files.fileHash(sourceTarball);
  var treeHash = files.treeHash(dirToTar);

  return {
    sourceTarball: sourceTarball,
    tarballHash: tarballHash,
    treeHash: treeHash
  };
};

var uploadTarball = function (putUrl, tarball) {
  var size = fs.statSync(tarball).size;
  var rs = fs.createReadStream(tarball);
  try {
    // Use getUrl instead of request, to throw on 4xx/5xx.
    httpHelpers.getUrl({
      method: 'PUT',
      url: putUrl,
      headers: {
        'content-length': size,
        'content-type': 'application/octet-stream',
        'x-amz-acl': 'public-read'
      },
      bodyStream: rs
    });
  } finally {
    rs.close();
  }
};

exports.uploadTarball = uploadTarball;

var bundleBuild = function (unipackage) {
  buildmessage.assertInJob();

  var tempDir = files.mkdtemp('build-package-');
  var packageTarName = unipackage.tarballName();
  var tarInputDir = path.join(tempDir, packageTarName);

  unipackage.saveToPath(tarInputDir, {
    // Don't upload buildinfo.json. It's only of interest locally (for example,
    // it contains a watchset with local paths).  (This also means we don't
    // need to specify a catalog, yay.)
    elideBuildInfo: true
  });

  var buildTarball = path.join(tempDir, packageTarName + '.tgz');
  files.createTarball(tarInputDir, buildTarball);

  var tarballHash = files.fileHash(buildTarball);
  var treeHash = files.treeHash(tarInputDir, {
    // We don't include any package.json from an npm module in the tree hash,
    // because npm isn't super consistent about what it puts in there (eg, does
    // it include the "readme" field)? This ends up leading to spurious
    // differences. The tree hash will still notice any actual CODE changes in
    // the npm packages.
    ignore: function (relativePath) {
      var pieces = relativePath.split(path.sep);
      return pieces.length && _.last(pieces) === 'package.json'
        && _.contains(pieces, 'npm');
    }
  });

  return {
    buildTarball: buildTarball,
    tarballHash: tarballHash,
    treeHash: treeHash
  };
};

exports.bundleBuild = bundleBuild;

var createAndPublishBuiltPackage = function (conn, unipackage) {
  buildmessage.assertInJob();

  // Note: we really want to do this before createPackageBuild, because the URL
  // we get from createPackageBuild will expire!
  Console.stdout.write('Bundling build...\n');
  var bundleResult = bundleBuild(unipackage);
  if (buildmessage.jobHasMessages())
    return;

  Console.stdout.write('Creating package build...\n');
  var uploadInfo = exports.callPackageServer(conn,
    'createPackageBuild', {
      packageName: unipackage.name,
      version: unipackage.version,
      buildArchitectures: unipackage.buildArchitectures()
  });

  Console.stdout.write('Uploading build...\n');
  uploadTarball(uploadInfo.uploadUrl,
                bundleResult.buildTarball);

  Console.stdout.write('Publishing package build...\n');
  exports.callPackageServer(conn,
            'publishPackageBuild',
            uploadInfo.uploadToken,
            bundleResult.tarballHash,
            bundleResult.treeHash);

  Console.stdout.write('Published ' + unipackage.name +
                       ', version ' + unipackage.version);

  Console.stdout.write('\nDone!\n');
};

exports.createAndPublishBuiltPackage = createAndPublishBuiltPackage;

exports.handlePackageServerConnectionError = function (error) {
  if (error instanceof AlreadyPrintedMessageError) {
    // do nothing
  } else if (error.errorType === 'Meteor.Error') {
    Console.stderr.write("Error from package server");
    if (error.message) {
      Console.stderr.write(": " + error.message);
    }
    Console.stderr.write("\n");
  } else if (error.errorType === "DDP.ConnectionError") {
    Console.stderr.write("Error connecting to package server: "
                         + error.message + "\n");
  } else {
    throw error;
  }
};

// Publish the package information into the server catalog. Create new records
// for the package (if needed), the version and the build; upload source and
// unipackage.
//
// packageSource: the packageSource for this package.
// compileResult: the compiled unipackage and various source files.
// conn: the open, logged-in connection over which we should talk to the package
//       server. DO NOT CLOSE this connection here.
// options:
//      new: this package is new, we should call createPackage to create a new
//           package record.
//      existingVersion: we expect the version to exist already, and for us
//           to merely be providing a new build of the same source
//
// Return true on success and an error code otherwise.
exports.publishPackage = function (packageSource, compileResult, conn, options) {
  buildmessage.assertInJob();

  options = options || {};

  if (options.new && options.existingVersion)
    throw Error("is it new or does it exist?!?");

  var name = packageSource.name;
  var version = packageSource.version;

  // Check that the package name is valid.
  try {
    utils.validatePackageName(name);
  } catch (e) {
    if (!e.versionParserError)
      throw e;
    Console.stderr.write(e.error + "\n");
    return 1;
  }

  // Check that we have a version.
  if (! version) {
    Console.stderr.write(
     "That package cannot be published because it doesn't have a version.\n");
    return 1;
  }

  // Check that the version description is under the character limit. (We check
  // all string limits on the server, but this is the one that is mostly likely
  // to be wrong)
  if (!packageSource.metadata.summary) {
    Console.stderr.write("Please describe what your package does. \n");
    Console.stderr.write("Set a summary in Package.describe in package.js. \n");
    return 1;
  }

  if (packageSource.metadata.summary &&
      packageSource.metadata.summary.length > 100) {
    Console.stderr.write("Description must be under 100 chars. \n");
    Console.stderr.write("Publish failed. \n");
    return 1;
  }

  var catalog = require('./catalog.js');

  // Check that we are an authorized maintainer of this package.
  if (!options['new']) {
    var packRecord = catalog.official.getPackage(name);
    if (!packRecord) {
      Console.stderr.write('There is no package named ' + name +
                           '. If you are creating a new package, use the --create flag. \n');
      Console.stderr.write("Publish failed. \n");
      return 1;
    }

    if (!exports.amIAuthorized(name, conn, false)) {
      Console.stderr.write('You are not an authorized maintainer of ' + name + ".\n");
      Console.stderr.write('Only authorized maintainers may publish new versions. \n');
      return 1;
    }
  }

  // Check that the package does not have any unconstrained references.
  var packageDeps =  packageSource.getDependencyMetadata();
  var badConstraints = [];
  _.each(packageDeps, function(refs, label) {
    // HACK: we automatically include the meteor package and there is no way for
    // anyone to set its dependency data correctly, so I guess we shouldn't
    // penalize the user for not doing that. It will be resolved at runtime
    // anyway.
    if (label !== "meteor" &&
        refs.constraint == null) {
      badConstraints.push(label);
    }
  });

  // If we are not a core package and some of our constraints are unspecified,
  // then we should force the user to specify them. This is because we are not
  // sure about pre-0.90 package versions yet.
  if (!packageSource.isCore && !_.isEqual(badConstraints, [])) {
    Console.stderr.write(
"You must specify a version constraint for the following packages:");
    _.each(badConstraints, function(bad) {
      Console.stderr.write(" " + bad);
    });
    process.exit(1);
  }

  // We need to build the test package to get all of its sources.
  var testFiles = [];
  var messages = buildmessage.capture(
    { title: "getting test sources" },
    function () {
      var testName = packageSource.testName;
      if (testName) {
        var PackageSource = require('./package-source.js');
        var compiler = require('./compiler.js');

        var testSource = new PackageSource(catalog.complete);
        // We need to pass in the name of the test package in order to
        // initialize it. Otherwise, the defaul behaviour will be to initalize
        // the base package.
        testSource.initFromPackageDir(packageSource.sourceRoot, {
          name: testName
        });
        if (buildmessage.jobHasMessages())
          return; // already have errors, so skip the build

        var testUnipackage = compiler.compile(testSource, { officialBuild: true });
        testFiles = testUnipackage.sources;
      }
    });

  if (messages.hasMessages()) {
    Console.stderr.write(messages.formatMessages());
    return 1;
  }

  Console.stdout.write('Bundling source...\n');

  var sources = _.union(compileResult.sources, testFiles);

  // Send the versions lock file over to the server! We should make sure to use
  // the same version lock file when we build this source elsewhere (ex:
  // publish-for-arch).
  // But see also #PackageVersionFilesHack
  var versionsFile = packageSource.versionsFilePath();
  if (versionsFile &&  fs.existsSync(versionsFile)) {
    sources.push("versions.json");
  }
  var sourceBundleResult = bundleSource(
    compileResult.unipackage, sources, packageSource.sourceRoot);

  // Create the package. Check that the metadata exists.
  if (options.new) {
    Console.stdout.write('Creating package...\n');
    try {
      var packageId = exports.callPackageServer(conn,
        'createPackage', {
            name: packageSource.name
        });
    } catch (err) {
      Console.stderr.write(err.message + "\n");
      return 3;
    }

  }

  if (options.existingVersion) {
    var existingRecord = catalog.official.getVersion(name, version);
    if (!existingRecord) {
      Console.stderr.write("Version does not exist.\n");
      return 1;
    }
    if (existingRecord.source.treeHash !== sourceBundleResult.treeHash) {
      Console.stderr.write(
        "Package source differs from the existing version.\n");
      return 1;
    }

    // XXX check that we're actually providing something new?
  } else {
    Console.stdout.write('Creating package version...\n');

    var uploadRec = {
      packageName: packageSource.name,
      version: version,
      description: packageSource.metadata.summary,
      git: packageSource.metadata.git,
      earliestCompatibleVersion: packageSource.earliestCompatibleVersion,
      compilerVersion: compiler.BUILT_BY,
      containsPlugins: packageSource.containsPlugins(),
      dependencies: packageDeps
    };
    try {
      var uploadInfo = exports.callPackageServer(conn,
        'createPackageVersion', uploadRec);
    } catch (err) {
      Console.stderr.write("ERROR " + err.message + "\n");
      return 3;
    }

    // XXX If package version already exists, print a nice error message
    // telling them to try 'meteor publish-for-arch' if they want to
    // publish a new build.

    Console.stdout.write('Uploading source...\n');
    uploadTarball(uploadInfo.uploadUrl, sourceBundleResult.sourceTarball);

    Console.stdout.write('Publishing package version...\n');
    try {
      exports.callPackageServer(conn,
                        'publishPackageVersion',
                        uploadInfo.uploadToken,
                        { tarballHash: sourceBundleResult.tarballHash,
                          treeHash: sourceBundleResult.treeHash });
    } catch (err) {
      Console.stderr.write("ERROR " + err.message + "\n");
      return 3;
    }

  }

  createAndPublishBuiltPackage(conn, compileResult.unipackage);

  return 0;
};

// Call the server to ask if we are authorized to update this release or
// package. This is a way to save time before sending data to the server. It
// will mostly ignore most errors (just in case we have a flaky network connection or
// something) and let the method deal with those.
//
// If this returns FALSE, then we are NOT authorized.
// Otherwise, return true.
exports.amIAuthorized = function (name, conn, isRelease) {
  var methodName = "amIAuthorized" +
    (isRelease ? "Release" : "Package");

  try {
    exports.callPackageServer(conn, methodName, name);
  } catch (err) {
    if (err.error === 401) {
      return false;
    }

    // We don't know what this error is. Probably we can't contact the server,
    // or the like. It would be a pity to fail all operations with the server
    // just because a preliminary check fails, so return true for now.
    return true;
  }
  return true;
};


Tinytest.add("absolute-url - basics", function(test) {

  _.each(['', 'http://'], function (prefix) {

    test.equal(Meteor.absoluteUrl({rootUrl: prefix + 'asdf.com'}),
               'http://asdf.com/');
    test.equal(Meteor.absoluteUrl(undefined, {rootUrl: prefix + 'asdf.com'}),
               'http://asdf.com/');
    test.equal(Meteor.absoluteUrl(undefined, {rootUrl: prefix + 'asdf.com/'}),
               'http://asdf.com/');

    test.equal(Meteor.absoluteUrl('foo', {rootUrl: prefix + 'asdf.com/'}),
               'http://asdf.com/foo');
    test.equal(Meteor.absoluteUrl('/foo', {rootUrl: prefix + 'asdf.com'}),
               'http://asdf.com//foo');
    test.equal(Meteor.absoluteUrl('#foo', {rootUrl: prefix + 'asdf.com'}),
               'http://asdf.com/#foo');

    test.equal(Meteor.absoluteUrl('foo', {rootUrl: prefix + 'asdf.com',
                                          secure: true}),
               'https://asdf.com/foo');
    test.equal(Meteor.absoluteUrl('foo', {rootUrl: 'https://asdf.com',
                                          secure: true}),
               'https://asdf.com/foo');
    test.equal(Meteor.absoluteUrl('foo', {rootUrl: 'https://asdf.com',
                                          secure: false}),
               'https://asdf.com/foo');

    test.equal(Meteor.absoluteUrl('foo', {rootUrl: prefix + 'localhost',
                                          secure: true}),
               'http://localhost/foo');
    test.equal(Meteor.absoluteUrl('foo', {rootUrl: prefix + 'localhost:3000',
                                          secure: true}),
               'http://localhost:3000/foo');
    test.equal(Meteor.absoluteUrl('foo', {rootUrl: 'https://localhost:3000',
                                          secure: true}),
               'https://localhost:3000/foo');
    test.equal(Meteor.absoluteUrl('foo', {rootUrl: prefix + '127.0.0.1:3000',
                                          secure: true}),
               'http://127.0.0.1:3000/foo');

    // test replaceLocalhost
    test.equal(Meteor.absoluteUrl('foo', {rootUrl: prefix + 'localhost:3000',
                                          replaceLocalhost: true}),
               'http://127.0.0.1:3000/foo');
    test.equal(Meteor.absoluteUrl('foo', {rootUrl: prefix + 'localhost',
                                          replaceLocalhost: true}),
               'http://127.0.0.1/foo');
    test.equal(Meteor.absoluteUrl('foo', {rootUrl: prefix + '127.0.0.1:3000',
                                          replaceLocalhost: true}),
               'http://127.0.0.1:3000/foo');
    test.equal(Meteor.absoluteUrl('foo', {rootUrl: prefix + '127.0.0.1',
                                          replaceLocalhost: true}),
               'http://127.0.0.1/foo');
    // don't replace just any localhost
    test.equal(Meteor.absoluteUrl('foo', {rootUrl: prefix + 'foo.com/localhost',
                                          replaceLocalhost: true}),
               'http://foo.com/localhost/foo');
    test.equal(Meteor.absoluteUrl('foo', {rootUrl: prefix + 'foo.localhost.com',
                                          replaceLocalhost: true}),
               'http://foo.localhost.com/foo');
  });
});

Tinytest.add("absolute-url - environment", function(test) {
  // make sure our test runner set the runtime configuration, and this
  // propagates to the client.
  test.isTrue(/^http/.test(__meteor_runtime_config__.ROOT_URL));
});


var queue = [];
var loaded = !Meteor.isCordova &&
  (document.readyState === "loaded" || document.readyState == "complete");

var ready = function() {
  loaded = true;
  while (queue.length)
    (queue.shift())();
};

if (document.addEventListener) {
  var event = Meteor.isCordova ? 'deviceready' : 'DOMContentLoaded';
  document.addEventListener(event, ready, false);
  window.addEventListener('load', ready, false);
} else {
  document.attachEvent('onreadystatechange', function () {
    if (document.readyState === "complete")
      ready();
  });
  window.attachEvent('load', ready);
}

/**
 * @summary Run code when a client or a server starts.
 * @locus Anywhere
 * @param {Function} func A function to run on startup.
 */
Meteor.startup = function (cb) {
  var doScroll = !document.addEventListener &&
    document.documentElement.doScroll;

  if (!doScroll || window !== top) {
    if (loaded)
      cb();
    else
      queue.push(cb);
  } else {
    try { doScroll('left'); }
    catch (e) {
      setTimeout(function() { Meteor.startup(cb); }, 50);
      return;
    };
    cb();
  }
};


syncToken._id = "1"; //Add fake _id so it fits the pattern

var lastSync = { timestamp: Date.now() };

getLoadPathForPackage: function (name, version, constraintSolverOpts) {

keep keep keep replace replace keep keep keep keep


/**
 * @summary Options to customize emails sent from the Accounts system.
 * @locus Anywhere
 */
Accounts.emailTemplates = {
  from: "Meteor Accounts <no-reply@meteor.com>",
  siteName: Meteor.absoluteUrl().replace(/^https?:\/\//, '').replace(/\/$/, ''),

  resetPassword: {
    subject: function(user) {
      return "How to reset your password on " + Accounts.emailTemplates.siteName;
    },
    text: function(user, url) {
      var greeting = (user.profile && user.profile.name) ?
            ("Hello " + user.profile.name + ",") : "Hello,";
      return greeting + "\n"
        + "\n"
        + "To reset your password, simply click the link below.\n"
        + "\n"
        + url + "\n"
        + "\n"
        + "Thanks.\n";
    }
  },
  verifyEmail: {
    subject: function(user) {
      return "How to verify email address on " + Accounts.emailTemplates.siteName;
    },
    text: function(user, url) {
      var greeting = (user.profile && user.profile.name) ?
            ("Hello " + user.profile.name + ",") : "Hello,";
      return greeting + "\n"
        + "\n"
        + "To verify your account email, simply click the link below.\n"
        + "\n"
        + url + "\n"
        + "\n"
        + "Thanks.\n";
    }
  },
  enrollAccount: {
    subject: function(user) {
      return "An account has been created for you on " + Accounts.emailTemplates.siteName;
    },
    text: function(user, url) {
      var greeting = (user.profile && user.profile.name) ?
            ("Hello " + user.profile.name + ",") : "Hello,";
      return greeting + "\n"
        + "\n"
        + "To start using the service, simply click the link below.\n"
        + "\n"
        + url + "\n"
        + "\n"
        + "Thanks.\n";
    }
  }
};


This file is just the same as `coffeescript_tests.coffee`, first we set a
property, which we check for in `coffeescript_tests.js`, and then a trivial
testcase.

    Meteor.__COFFEEMDSCRIPT_PRESENT = true
    Tinytest.add "markdown coffeescript - compile", (test) -> test.isTrue true


// We always write to the data store; the fact there is no data is itself data!

// If there is no new data from the server, don't bother writing things to

// disk (unless we were just told to reset everything).

var syncComplete = _.isEqual(remoteData.collections, {}) || remoteData.upToDate;

var fs = require('fs');
var path = require('path');
var Future = require('fibers/future');
var _ = require('underscore');
var auth = require('./auth.js');
var httpHelpers = require('./http-helpers.js');
var release = require('./release.js');
var files = require('./files.js');
var ServiceConnection = require('./service-connection.js');
var utils = require('./utils.js');
var buildmessage = require('./buildmessage.js');
var compiler = require('./compiler.js');
var uniload = require('./uniload.js');
var tropohouse = require('./tropohouse.js');
var config = require('./config.js');
var semver = require('semver');
var packageClient = require('./package-client.js');
var sqlite3 = require('sqlite3');
var archinfo = require('./archinfo.js');

// XXX: Rationalize these flags.  Maybe use the logger?
DEBUG_SQL = !!process.env.METEOR_DEBUG_SQL;

METADATA_LAST_SYNC = "lastsync";

var Db = function (dbFile, options) {
  var self = this;

  self._dbFile = dbFile;

  self._autoPrepare = true;
  self._prepared = {};

  self._db = self.open(dbFile);
};

_.extend(Db.prototype, {
  runInTransaction: function (action) {
    var self = this;
    var future = new Future();
    // XXX: serialize doesn't work with fibers
    self._db.serialize(function () {
      var t1 = Date.now();

      var rollback = true;
      var result = null;
      var resultError = null;

      self.execute("BEGIN IMMEDIATE TRANSACTION");
      try {
        result = action(self);
        rollback = false;
      } catch (err) {
        resultError = err;
      }

      if (!rollback) {
        try {
          self.execute("END TRANSACTION");
        } catch (err) {
          rollback = true;
          console.log("Transaction commit failed ", err);
          if (!resultError) {
            resultError = err;
          }
        }
      }

      if (rollback) {
        try {
          self.execute("ROLLBACK TRANSACTION");
        } catch (err) {
          // Now we are out of options...
          console.log("Transaction rollback failed ", err);
          if (!resultError) {
            resultError = err;
          }
        }
      }

      if (DEBUG_SQL) {
        var t2 = Date.now();
        console.log("Transaction took: ", (t2 - t1));
      }

      if (resultError) {
        future['throw'](resultError);
      } else {
        future['return'](result);
      }
    });
    return future.wait();
  },

  open: function (dbFile) {
    var self = this;

    if ( !fs.existsSync(path.dirname(dbFile)) ) {
      var folder = path.dirname(dbFile);
      if ( !files.mkdir_p(folder) )
        throw new Error("Could not create folder at " + folder);
    }

    console.log("Opening db file", dbFile);
    return new sqlite3.Database(dbFile);
  },

  // Runs a query synchronously, returning all rows
  query: function (sql, params) {
    var self = this;

    var prepared = null;
    if (false && self._autoPrepare) {
      prepared = self._prepareWithCache(sql);
    }

    if (DEBUG_SQL) {
      var t1 = Date.now();
    }

    var future = new Future();

    //console.log("Executing SQL ", sql);

    var callback = function (err, rows) {
      if (err) {
        future['throw'](err);
      } else {
        future['return'](rows);
      }
    };

    if (prepared) {
      prepared.all(sql, params, callback);
    } else {
      self._db.all(sql, params, callback);
    }

    var ret = future.wait();

    if (DEBUG_SQL) {
      var t2 = Date.now();
      if ((t2 - t1) > 10) {
        console.log("SQL statement ", sql, " took ", (t2 - t1));
      }
    }

    return ret;
  },

  // Runs a query synchronously, returning no rows
  execute: function (sql, params) {
    var self = this;

    var prepared = null;
    if (self._autoPrepare) {
      prepared = self._prepareWithCache(sql);
    }

    if (DEBUG_SQL) {
      var t1 = Date.now();
    }

    var future = new Future();

    //console.log("Executing SQL ", sql);

    var callback = function (err, rows) {
      if (err) {
        future['throw'](err);
      } else {
        future['return'](rows);
      }
    };

    if (prepared) {
      prepared.run(params, callback);
    } else {
      self._db.run(sql, params, callback);
    }

    var ret = future.wait();

    if (DEBUG_SQL) {
      var t2 = Date.now();
      if ((t2 - t1) > 10) {
        console.log("SQL statement ", sql, " took ", (t2 - t1));
      }
    }

    return ret;
  },

  // Prepares the statement, caching the result
  _prepareWithCache: function (sql) {
    var self = this;

    var prepared = self._prepared[sql];
    if (!prepared) {
      //console.log("Preparing statement: ", sql);
      var future = new Future();
      prepared = self._db.prepare(sql, function (err) {
        if (err) {
          future['throw'](err);
        } else {
          future['return']();
        }
      });
      future.wait();
      self._prepared[sql] = prepared;
    }
    return prepared;
  }

});

var Table = function (name, jsonFields) {
  var self = this;

  self.name = name;
  self.jsonFields = jsonFields;

  self._buildStatements();
};

_.extend(Table.prototype, {
  _buildStatements: function () {
    var self = this;

    var queryParams = self._generateQuestionMarks(self.jsonFields.length + 1);
    self._selectQuery = "SELECT * FROM " + self.name + " WHERE _id=?";
    self._insertQuery = "INSERT INTO " + self.name + " VALUES " + queryParams;
    self._deleteQuery = "DELETE FROM " + self.name + " WHERE _id=?";
  },

  //Generate a string of the form (?, ?) where the n is the number of question mark
  _generateQuestionMarks: function (n) {
    return "(" + _.times(n, function () { return "?" }).join(",") + ")";
  },

  find: function (txn, id) {
    var self = this;
    var rows = txn.query(self._selectQuery, [ id ]);
    if (rows.length !== 0) {
      if (rows.length !== 1) {
        throw new Error("Corrupt database (PK violation)");
      }
      return rows[0];
    }
    return undefined;
  },

  upsert: function (txn, objects) {
    var self = this;

    // XXX: Use sqlite upsert
    // XXX: Speculative insert
    // XXX: Fix transaction logic so we always roll back
    _.each(objects, function (o) {
      var id = o._id;
      var rows = txn.query(self._selectQuery, [ id ]);
      if (rows.length !== 0) {
        txn.execute(self._deleteQuery, [ id ]);
      }
      var row = [];
      _.each(self.jsonFields, function (jsonField) {
        row.push(o[jsonField]);
      });
      row.push(JSON.stringify(o));
      txn.execute(self._insertQuery, row);
    });
  },

  createTable: function (txn) {
    var self = this;

    var sql = 'CREATE TABLE IF NOT EXISTS ' + self.name + '(';
    for (var i = 0; i < self.jsonFields.length; i++) {
      var jsonField = self.jsonFields[i];
      var sqlColumn = jsonField;
      if (i != 0) sql += ",";
      sql += sqlColumn + " STRING";
      if (sqlColumn === '_id') {
        sql += " PRIMARY KEY";
      }
    }
    sql += ", content STRING";
    sql += ")";
    txn.execute(sql);

    //sql = "CREATE INDEX IF NOT EXISTS idx_" + self.name + "_id ON " + self.name + "(_id)";
    //txn.execute(sql);
  }
});

// A RemoteCatalog is a local cache of the content of troposphere.
// A default instance of this catalog is registered by the layered catalog and is available
// under the variable "official" from the catalog.js
//
// The remote catalog is backed by a db to make things easier on the memory and for faster queries
var RemoteCatalog = function (options) {
  var self = this;

  // Set this to true if we are not going to connect to the remote package
  // server, and will only use the cached data for our package information
  // This means that the catalog might be out of date on the latest developments.
  self.offline = null;

  self.options = options || {};

  self.db = null;
  self._currentRefreshIsLoud = false;
};

_.extend(RemoteCatalog.prototype, {
  getVersion: function (name, version) {
    var result = this._queryAsJSON("SELECT content FROM versions WHERE packageName=? AND version=?", [name, version]);
    if(!result || result.length === 0) {
      return null;
    }
    return result[0];
  },

  getSortedVersions: function (name) {
    var self = this;
    var match = this._getPackages(name);
    if (match === null)
      return [];
    return _.pluck(match, 'version').sort(semver.compare);
  },

  getLatestMainlineVersion: function (name) {
    var self = this;
    var versions = self.getSortedVersions(name);
    versions.reverse();
    var latest = _.find(versions, function (version) {
      return !/-/.test(version);
    });
    if (!latest)
      return null;
    return self.getVersion(name, latest);
  },

  getPackage: function (name) {
    var result = this._getPackages(name);
    if (!result || result.length === 0)
      return null;
    return result[0];
  },

  _getPackages: function (name) {
    if (!name) {
      throw new Error("No name provided");
    }
    return this._queryAsJSON("SELECT content FROM versions WHERE packageName=?", name);
  },

  getAllBuilds: function (name, version) {
    var result = this._queryAsJSON("SELECT * FROM builds WHERE builds.versionId = (SELECT _id FROM versions WHERE versions.packageName=? AND versions.version=?)", [name, version]);
    if (!result || result.length === 0)
      return null;
    return result;
  },

  getBuildsForArches: function (name, version, arches) {
    var self = this;

    var solution = null;
    var allBuilds = self.getAllBuilds(name, version);

    utils.generateSubsetsOfIncreasingSize(allBuilds, function (buildSubset) {
      // This build subset works if for all the arches we need, at least one
      // build in the subset satisfies it. It is guaranteed to be minimal,
      // because we look at subsets in increasing order of size.
      var satisfied = _.all(arches, function (neededArch) {
        return _.any(buildSubset, function (build) {
          var buildArches = build.buildArchitectures.split('+');
          return !!archinfo.mostSpecificMatch(neededArch, buildArches);
        });
      });
      if (satisfied) {
        solution = buildSubset;
        return true;  // stop the iteration
      }
    });
    return solution;  // might be null!
  },

  // Returns general (non-version-specific) information about a
  // release track, or null if there is no such release track.
  getReleaseTrack: function (name) {
    var self = this;
    var result = self._queryAsJSON("SELECT content FROM releaseTracks WHERE name=?", name);
    if (!result || result.length === 0)
      return null;
    return result[0];
  },

  getReleaseVersion: function (track, version) {
    var self = this;
    var result = self._queryAsJSON("SELECT content FROM releaseVersions WHERE track=? AND version=?", [track, version]);
    if (!result || result.length === 0)
      return null;
    return result[0];
  },

  getAllReleaseTracks: function () {
    return _.pluck(this._queryWithRetry("SELECT name FROM releaseTracks"), 'name');
  },

  getAllPackageNames: function () {
    return _.pluck(this._queryWithRetry("SELECT name FROM packages"), 'name');
  },

  initialize: function (options) {
    var self = this;

    options = options || {};
    // We should to figure out if we are intending to connect to the package server.
    self.offline = options.offline ? options.offline : false;

    var dbFile = options.packageStorage || config.getPackageStorage();
    self.db = new Db(dbFile);

    self.tableVersions = new Table('versions', ['packageName', 'version', '_id']);
    self.tableBuilds = new Table('builds', ['versionId', '_id']);
    self.tableReleaseTracks = new Table('releaseTracks', ['name', '_id']);
    self.tableReleaseVersions = new Table('releaseVersions', ['track', 'version', '_id']);
    self.tablePackages = new Table('packages', ['name', '_id']);
    self.tableSyncToken = new Table('syncToken', ['_id']);
    self.tableMetadata = new Table('metadata', ['_id']);

    self.allTables = [ self.tableVersions,
      self.tableBuilds,
      self.tableReleaseTracks,
      self.tableReleaseVersions,
      self.tablePackages,
      self.tableSyncToken,
      self.tableMetadata ]
    return self.db.runInTransaction(function(txn) {
      _.each(self.allTables, function (table) {
        table.createTable(txn);
      });

      // Extra indexes for the most expensive queries
      // These are non-unique indexes
      txn.execute("CREATE INDEX IF NOT EXISTS versionsNamesIdx ON versions(packageName)");
      txn.execute("CREATE INDEX IF NOT EXISTS buildsVersionsIdx ON builds(versionId)");
    });
  },

  // This function empties the DB. This is called from the package-client.
  reset: function () {
    var self = this;
    return self.db.runInTransaction(function (txn) {
      _.each(self.allTables, function (table) {
        txn.execute("DELETE FROM " + table.name);
      });
    });
  },

  refresh: function (options) {
    var self = this;
    options = options || {};

    console.log("In remote catalog refresh");

    buildmessage.assertInCapture();
    if (self.offline)
      return;

    if (options.maxAge) {
      var lastSync = self.getMetadata(METADATA_LAST_SYNC);
      console.log("lastSync = ", lastSync);
      if (lastSync && lastSync.timestamp) {
        if ((Date.now() - lastSync.timestamp) < options.maxAge) {
          console.log("Catalog is sufficiently up-to-date");
          return;
        }
      }
    }

    if (!options.silent) {
      self._currentRefreshIsLoud = true;
    }

    var updateResult = {};
    buildmessage.enterJob({ title: 'Refreshing package metadata.' }, function () {
      updateResult = packageClient.updateServerPackageData(self);
    });
    if (!updateResult.data) {
      process.stderr.write("Warning: could not connect to package server\n");
    }
    if (updateResult.resetData) {
      tropohouse.default.wipeAllPackages();
      self.reset();
    }

  },

  // Given a release track, return all recommended versions for this track, sorted
  // by their orderKey. Returns the empty array if the release track does not
  // exist or does not have any recommended versions.
  getSortedRecommendedReleaseVersions: function (track, laterThanOrderKey) {
    var self = this;
    var result = self._queryAsJSON("SELECT content FROM releaseVersions WHERE track=?", track);

    var recommended = _.filter(result, function (v) {
      if (!v.recommended)
        return false;
      return !laterThanOrderKey || v.orderKey > laterThanOrderKey;
    });

    var recSort = _.sortBy(recommended, function (rec) {
      return rec.orderKey;
    });
    recSort.reverse();
    return _.pluck(recSort, "version");
  },

  getDefaultReleaseVersion: function (track) {
    var self = this;

    if (!track)
      track = exports.DEFAULT_TRACK;

    var versions = self.getSortedRecommendedReleaseVersions(track);
    if (!versions.length)
      return null;
    return {track: track, version: versions[0]};
  },

  getBuildWithPreciseBuildArchitectures: function (versionRecord, buildArchitectures) {
    var self = this;
    var matchingBuilds = this._queryAsJSON("SELECT content FROM builds WHERE versionId=?", versionRecord._id);
    return _.findWhere(matchingBuilds, { buildArchitectures: buildArchitectures });
  },

  isLocalPackage: function() {
    return false;
  },

  _queryWithRetry: function (query, values, options) {
    var self = this;
    var result = self._justQuery(query, values);
    if ( result.length !== 0 || ( options && options.noRetry ) )
      return result;
    console.log("Forcing refresh because of _queryWithRetry", query, values);
    self.refresh();
    return self._justQuery(query, values);
  },

  // Runs a query synchronously.
  // Query is the sql query to be executed and values are the parameters of the query
  _justQuery: function (query, values) {
    var self = this;
    var results;
    try {
      results = self.db.query(query, values);
    } catch (err) {
      // XXX: Throw?
      console.log("Error running query ", query, err);
      results = [];
    }
    if (results !== [])
      return results;
    // XXX: Scary
    console.log("Forcing refresh in _justQuery");
    self.refresh();
    // XXX: What should we return here??
    return results;
  },

  // Execute a query using the values as arguments of the query and return the result as JSON.
  // This code assumes that the table being queried always have a column called "entity"
  _queryAsJSON: function (query, values, options) {
    var self = this;
    console.log("Executing query with _queryAsJSON: ", query);
    var rows = self._queryWithRetry(query, values, options);
    return _.map(rows, function(entity) {
      return JSON.parse(entity.content);
    });
  },

  //Given data from troposphere, add it into the local store
  insertData: function(serverData) {
    var self = this;
    return self.db.runInTransaction(function (txn) {
      self.tablePackages.upsert(txn, serverData.collections.packages);
      self.tableBuilds.upsert(txn, serverData.collections.builds);
      self.tableVersions.upsert(txn, serverData.collections.versions);
      self.tableReleaseTracks.upsert(txn, serverData.collections.releaseTracks);
      self.tableReleaseVersions.upsert(txn, serverData.collections.releaseVersions);

      var syncToken = serverData.syncToken;
      console.log("Adding syncToken: ", JSON.stringify(syncToken));
      syncToken._id = "1"; //Add fake _id so it fits the pattern
      self.tableSyncToken.upsert(txn, [syncToken]);

      var lastSync = { timestamp: Date.now() };
      self._setMetadata(txn, METADATA_LAST_SYNC, lastSync);
    });
  },

  getLoadPathForPackage: function (name, version, constraintSolverOpts) {
    var packageDir = tropohouse.default.packagePath(name, version);
    if (fs.existsSync(packageDir)) {
      return packageDir;
    }
    return null;
  },

  getSyncToken: function() {
    var self = this;
    var result = self._queryAsJSON("SELECT content FROM syncToken", [], { noRetry: true });
    if (!result || result.length === 0) {
      console.log("No sync token found");
      return {};
    }
    if (result.length !== 1) {
      throw new Error("Unexpected number of sync tokens found");
    }
    delete result[0]._id;
    //console.log(utils.getStackTrace());
    console.log("Returning sync token: " + JSON.stringify(result[0]));
    return result[0];
  },

  getMetadata: function(key) {
    var self = this;
    var row = self.tableMetadata.find(self.db, key);
    if (row) {
      return JSON.parse(row['content']);
    }
    return undefined;
  },

  setMetadata: function(key, value) {
    var self = this;
    self.db.runInTransaction(function (txn) {
      self._setMetadata(txn, key, value);
    });
  },

  _setMetadata: function(txn, key, value) {
    var self = this;
    value._id = key;
    self.tableMetadata.upsert(txn, [value]);
  }
});

exports.RemoteCatalog = RemoteCatalog;

//We put this constant here because we don't have any better place that would otherwise cause a cycle
exports.DEFAULT_TRACK = 'METEOR';

//The catalog as provided by troposhere (aka atomospherejs.com)
exports.official = new RemoteCatalog();


var fs = require('fs');
var _ = require('underscore');
var sourcemap = require('source-map');
var buildmessage = require('./buildmessage');

var packageDot = function (name) {
  if (/^[a-zA-Z0-9]*$/.exec(name))
    return "Package." + name;
  else
    return "Package['" + name + "']";
};

///////////////////////////////////////////////////////////////////////////////
// Module
///////////////////////////////////////////////////////////////////////////////

// options include name, imports, exports, useGlobalNamespace,
// combinedServePath, and importStubServePath, all of which have the
// same meaning as they do when passed to import().
var Module = function (options) {
  var self = this;

  // module name or null
  self.name = options.name || null;

  // files in the module. array of File
  self.files = [];

  // options
  self.declaredExports = options.declaredExports;
  self.useGlobalNamespace = options.useGlobalNamespace;
  self.combinedServePath = options.combinedServePath;
  self.importStubServePath = options.importStubServePath;
  self.jsAnalyze = options.jsAnalyze;
};

_.extend(Module.prototype, {
  // source: the source code
  // servePath: the path where it would prefer to be served if possible
  addFile: function (inputFile) {
    var self = this;
    self.files.push(new File(inputFile, self));
  },

  maxLineLength: function (ignoreOver) {
    var self = this;

    var maxInFile = [];
    _.each(self.files, function (file) {
      var m = 0;
      _.each(file.source.split('\n'), function (line) {
        if (line.length <= ignoreOver && line.length > m)
          m = line.length;
      });
      maxInFile.push(m);
    });

    return _.max(maxInFile);
  },

  // Figure out which vars need to be specifically put in the module
  // scope.
  computeAssignedVariables: function () {
    var self = this;

    if (!self.jsAnalyze) {
      // We don't have access to static analysis, probably because we *are* the
      // js-analyze package.  Let's do a stupid heuristic: the exports are
      // the only module scope vars. (This works for js-analyze.JSAnalyze...)
      return self.declaredExports;
    }

    // Find all global references in any files
    var assignedVariables = [];
    _.each(self.files, function (file) {
      assignedVariables = assignedVariables.concat(
        file.computeAssignedVariables());
    });
    assignedVariables = _.uniq(assignedVariables);

    return _.isEmpty(assignedVariables) ? undefined : assignedVariables;
  },

  // Output is a list of objects with keys 'source', 'servePath', 'sourceMap',
  // 'sourcePath'
  getPrelinkedFiles: function () {
    var self = this;

    // If there are no files *and* we are a no-exports-at-all unibuild, then
    // generate no prelink output.
    //
    // If there are no files, but we are a non-test package (and thus
    // self.declaredExports is an actual, albeit potentially empty, list), we
    // DON'T want to take this path: we want to return an empty prelink file, so
    // that at link time we end up at least setting `Package.foo = {}`.
    if (_.isEmpty(self.files) && !self.declaredExports)
      return [];

    // If we don't want to create a separate scope for this module,
    // then our job is much simpler. And we can get away with
    // preserving the line numbers.
    if (self.useGlobalNamespace) {
      return _.map(self.files, function (file) {
        var node = file.getPrelinkedOutput({ preserveLineNumbers: true });
        var results = node.toStringWithSourceMap({
          file: file.servePath
        }); // results has 'code' and 'map' attributes

        var sourceMap = results.map.toJSON();
        // No use generating empty source maps.
        if (_.isEmpty(sourceMap.sources))
          sourceMap = null;
        else
          sourceMap = JSON.stringify(sourceMap);

        return {
          source: results.code,
          servePath: file.servePath,
          sourceMap: sourceMap
        };
      });
    }

    // Otherwise..

    // Find the maximum line length.
    var sourceWidth = _.max([68, self.maxLineLength(120 - 2)]);

    // Prologue
    var chunks = [];

    // Emit each file
    _.each(self.files, function (file) {
      if (!_.isEmpty(chunks))
        chunks.push("\n\n\n\n\n\n");
      chunks.push(file.getPrelinkedOutput({ sourceWidth: sourceWidth }));
    });

    var node = new sourcemap.SourceNode(null, null, null, chunks);

    var results = node.toStringWithSourceMap({
      file: self.combinedServePath
    }); // results has 'code' and 'map' attributes
    return [{
      source: results.code,
      servePath: self.combinedServePath,
      sourceMap: results.map.toString()
    }];
  }
});

// Given 'symbolMap' like {Foo: 's1', 'Bar.Baz': 's2', 'Bar.Quux.A': 's3', 'Bar.Quux.B': 's4'}
// return something like
// {Foo: 's1', Bar: {Baz: 's2', Quux: {A: 's3', B: 's4'}}}
//
// If the value of a symbol in symbolMap is set null, then we just
// ensure that its parents exist. For example, {'A.B.C': null} means
// to make sure that symbol tree contains at least {A: {B: {}}}.
var buildSymbolTree = function (symbolMap) {
  var ret = {};

  _.each(symbolMap, function (value, symbol) {
    var parts = symbol.split('.');
    var lastPart = parts.pop();

    var walk = ret;
    _.each(parts, function (part) {
      if (! (part in walk))
        walk[part] = {};
      walk = walk[part];
    });

    if (value)
      walk[lastPart] = value;
  });

  return ret;
};

// Given something like {Foo: 's1', Bar: {Baz: 's2', Quux: {A: 's3', B: 's4'}}}
// construct a string like {Foo: s1, Bar: {Baz: s2, Quux: {A: s3, B: s4}}}
// except with pretty indentation.
var writeSymbolTree = function (symbolTree, indent) {
  var put = function (node, indent) {
    if (typeof node === "string") {
      return node;
    }
    if (_.keys(node).length === 0) {
      return '{}';
    }
    var spacing = new Array(indent + 1).join(' ');
    // XXX prettyprint!
    return "{\n" +
      _.map(node, function (value, key) {
        return spacing + "  " + key + ": " + put(value, indent + 2);
      }).join(',\n') + "\n" + spacing + "}";
  };

  return put(symbolTree, indent || 0);
};

///////////////////////////////////////////////////////////////////////////////
// File
///////////////////////////////////////////////////////////////////////////////

var File = function (inputFile, module) {
  var self = this;

  // source code for this file (a string)
  self.source = inputFile.source;

  // the path where this file would prefer to be served if possible
  self.servePath = inputFile.servePath;

  // The relative path of this input file in its source tree (eg,
  // package or app). Used for source maps, error messages..
  self.sourcePath = inputFile.sourcePath;

  // If true, don't wrap this individual file in a closure.
  self.bare = !!inputFile.bare;

  // A source map (generated by something like CoffeeScript) for the input file.
  self.sourceMap = inputFile.sourceMap;

  // The Module containing this file.
  self.module = module;
};

_.extend(File.prototype, {
  // Return the globals in this file as an array of symbol names.  For
  // example: if the code references 'Foo.bar.baz' and 'Quux', and
  // neither are declared in a scope enclosing the point where they're
  // referenced, then globalReferences would include ["Foo", "Quux"].
  computeAssignedVariables: function () {
    var self = this;

    var jsAnalyze = self.module.jsAnalyze;
    // If we don't have a JSAnalyze object, we probably are the js-analyze
    // package itself. Assume we have no global references. At the module level,
    // we'll assume that exports are global references.
    if (!jsAnalyze)
      return [];

    try {
      return _.keys(jsAnalyze.findAssignedGlobals(self.source));
    } catch (e) {
      if (!e.$ParseError)
        throw e;

      var errorOptions = {
        file: self.sourcePath,
        line: e.lineNumber,
        column: e.column
      };
      if (self.sourceMap) {
        var parsed = new sourcemap.SourceMapConsumer(self.sourceMap);
        var original = parsed.originalPositionFor(
          {line: e.lineNumber, column: e.column - 1});
        if (original.source) {
          errorOptions.file = original.source;
          errorOptions.line = original.line;
          errorOptions.column = original.column + 1;
        }
      }

      buildmessage.error(e.description, errorOptions);

      // Recover by pretending that this file is empty (which
      // includes replacing its source code with '' in the output)
      self.source = "";
      self.sourceMap = null;
      return [];
    }
  },

  // Relative path to use in source maps to indicate this file. No
  // leading slash.
  _pathForSourceMap: function () {
    var self = this;

    if (self.module.name)
      return self.module.name + "/" + self.sourcePath;
    else
      return require('path').basename(self.sourcePath);
  },

  // Options:
  // - preserveLineNumbers: if true, decorate minimally so that line
  //   numbers don't change between input and output. In this case,
  //   sourceWidth is ignored.
  // - sourceWidth: width in columns to use for the source code
  //
  // Returns a SourceNode.
  getPrelinkedOutput: function (options) {
    var self = this;

    // The newline after the source closes a '//' comment.
    if (options.preserveLineNumbers) {
      // Ugly version
      var mapNode;
      if (self.sourceMap) {
        mapNode = sourcemap.SourceNode.fromStringWithSourceMap(
          self.source, new sourcemap.SourceMapConsumer(self.sourceMap));
      } else {
        // This is an app file that was always JS. The output file here is going
        // to be the same name as the input file (because _pathForSourceMap in
        // apps is the basename of the source file), and having a JS file
        // pointing to a source map pointing to a JS file of the same name will
        // (a) be confusing (b) be unnecessary since we aren't renumbering
        // anything and (c) confuse at least Chrome.
        mapNode = self.source;
      }

      return new sourcemap.SourceNode(null, null, null, [
        self.bare ? "" : "(function(){",
        mapNode,
        (self.source.length && self.source[self.source.length - 1] !== '\n'
         ? "\n" : ""),
        self.bare ? "" : "\n})();\n"
      ]);
    }

    // Pretty version
    var chunks = [];

    // Prologue
    if (! self.bare)
      chunks.push("(function () {\n\n");

    // Banner
    var bannerLines = [self.servePath.slice(1)];
    if (self.bare) {
      bannerLines.push(
        "This file is in bare mode and is not in its own closure.");
    }
    var width = options.sourceWidth || 70;
    var bannerWidth = width + 3;
    var padding = bannerPadding(bannerWidth);
    chunks.push(banner(bannerLines, bannerWidth));
    var blankLine = new Array(width + 1).join(' ') + " //\n";
    chunks.push(blankLine);

    // Code, with line numbers
    // You might prefer your line numbers at the beginning of the
    // line, with /* .. */. Well, that requires parsing the source for
    // comments, because you have to do something different if you're
    // already inside a comment.

    var numberifyLines = function (f) {
      var num = 1;
      var lines = self.source.split('\n');
      _.each(lines, function (line) {
        var suffix = "\n";

        if (line.length <= width && line[line.length - 1] !== "\\") {
          suffix = padding.slice(line.length, width) + " // " + num + "\n";
        }
        f(line, suffix, num);
        num++;
      });
    };

    var lines = self.source.split('\n');

    if (self.sourceMap) {
      var buf = "";
      numberifyLines(function (line, suffix) {
        buf += line;
        buf += suffix;
      });
      // The existing source map is valid because all we're doing is adding
      // things to the end of lines, which doesn't affect the source map.  (If
      // we wanted to be picky, we could add some explicitly non-mapped regions
      // to the source map to cover the suffixes, which would make this
      // equivalent to the "no source map coming in" case, but this doesn't seem
      // that important.)
      chunks.push(sourcemap.SourceNode.fromStringWithSourceMap(
        self.source,
        new sourcemap.SourceMapConsumer(self.sourceMap)));
    } else {
      // There are probably ways to make a more compact source map. For example,
      // the only change we make is to append a comment, so we can probably emit
      // one mapping for the whole file. For the moment, we'll do it by the book
      // just to see how it goes.
      numberifyLines(function (line, suffix, num) {
        chunks.push(new sourcemap.SourceNode(num, 0, self._pathForSourceMap(),
                                             line));
        chunks.push(suffix);
      });
    }

    // Footer
    if (! self.bare)
      chunks.push(dividerLine(bannerWidth) + "\n}).call(this);\n");

    var node = new sourcemap.SourceNode(null, null, null, chunks);

    // If we're working directly from the original source here (and not from the
    // output of a transformation that had a source map), include the original
    // source in the source map. (If we are working on generated code, the
    // source map we received should have already contained the original
    // source.)
    if (!self.sourceMap)
      node.setSourceContent(self._pathForSourceMap(), self.source);

    return node;
  }
});

// Given a list of lines (not newline-terminated), returns a string placing them
// in a pretty banner of width bannerWidth. All lines must have length at most
// (bannerWidth - 6); if bannerWidth is not provided, the smallest width that
// fits is used.
var banner = function (lines, bannerWidth) {
  if (!bannerWidth)
    bannerWidth = 6 + _.max(lines, function (x) { return x.length; }).length;

  var divider = dividerLine(bannerWidth);
  var spacer = "// " + new Array(bannerWidth - 6 + 1).join(' ') + " //\n";
  var padding = bannerPadding(bannerWidth);

  var buf = divider + spacer;
  _.each(lines, function (line) {
    buf += "// " + (line + padding).slice(0, bannerWidth - 6) + " //\n";
  });
  buf += spacer + divider;
  return buf;
};
var dividerLine = function (bannerWidth) {
  return new Array(bannerWidth + 1).join('/') + "\n";
};
var bannerPadding = function (bannerWidth) {
  return new Array(bannerWidth + 1).join(' ');
};

///////////////////////////////////////////////////////////////////////////////
// Top-level entry point
///////////////////////////////////////////////////////////////////////////////

// This does the first phase of linking. It does not require knowledge
// of your imports. It returns the module's exports, plus a set of
// partially linked files which you must pass to link() along with
// your import list to get your final linked files.
//
// options include:
//
// name: the name of this module (for stashing exports to be later
// read by the imports of other modules); null if the module has no
// name (in that case exports will not work properly)
//
// inputFiles: an array of objects representing input files.
//  - source: the source code
//  - servePath: the path where it would prefer to be served if
//    possible. still allowed on non-browser targets, where it
//    represent as hint as to what the file should be named on disk in
//    the bundle (this will only be seen by someone looking at the
//    bundle, not in error messages, but it's still nice to make it
//    look good)
//  - sourcePath: path to use in error messages
//  - sourceMap: an optional source map (as string) for the input file
//
// declaredExports: an array of symbols that the module exports. Symbols are
// {name,testOnly} pairs.
//
// useGlobalNamespace: make the top level namespace be the same as the
// global namespace, so that symbols are accessible from the
// console. typically used when linking apps (as opposed to packages).
//
// combinedServePath: if we end up combining all of the files into
// one, use this as the servePath.
//
// importStubServePath: if useGlobalNamespace is true, then to
// preserve line numbers, we may want to emit an additional file
// containing import setup code for the global environment. this is
// the servePath to use for it.
//
// jsAnalyze: if possible, the JSAnalyze object from the js-analyze
// package. (This is not possible if we are currently linking the main unibuild of
// the js-analyze package!)
//
// Output is an object with keys:
// - files: is an array of output files in the same format as inputFiles
//   - EXCEPT THAT, for now, sourcePath is omitted and is replaced with
//     sourceMap (a string) (XXX)
// - assignedPackageVariables: an array of variables assigned to without
//   being declared
var prelink = function (options) {
  var module = new Module({
    name: options.name,
    declaredExports: options.declaredExports,
    useGlobalNamespace: options.useGlobalNamespace,
    importStubServePath: options.importStubServePath,
    combinedServePath: options.combinedServePath,
    jsAnalyze: options.jsAnalyze
  });

  _.each(options.inputFiles, function (inputFile) {
    module.addFile(inputFile);
  });

  // Do static analysis to compute module-scoped variables. Error recovery from
  // the static analysis mutates the sources, so this has to be done before
  // concatenation.
  var assignedVariables = module.computeAssignedVariables();
  var files = module.getPrelinkedFiles();

  return {
    files: files,
    assignedVariables: assignedVariables
  };
};

var SOURCE_MAP_INSTRUCTIONS_COMMENT = banner([
  "This is a generated file. You can view the original",
  "source in your browser if your browser supports source maps.",
  "",
  "If you are using Chrome, open the Developer Tools and click the gear",
  "icon in its lower right corner. In the General Settings panel, turn",
  "on 'Enable source maps'.",
  "",
  "If you are using Firefox 23, go to `about:config` and set the",
  "`devtools.debugger.source-maps-enabled` preference to true.",
  "(The preference should be on by default in Firefox 24; versions",
  "older than 23 do not support source maps.)"
]);

// Finish the linking.
//
// options include:
//
// imports: symbols to import. map from symbol name (something like
// 'Foo', "Foo.bar", etc) to the module from which it should be
// imported (which must load before us at runtime)
//
// packageVariables: package-scope variables, some of which may be exports.
//   a list of {name, export} objects; any non-falsy value for "export" means
//   to export it.
//
// useGlobalNamespace: must be the same value that was passed to link()
//
// prelinkFiles: the 'files' output from prelink()
//
// Output is an array of final output files in the same format as the
// 'inputFiles' argument to prelink().
var link = function (options) {
  if (options.useGlobalNamespace) {
    var ret = [];
    if (!_.isEmpty(options.imports)) {
      ret.push({
        source: getImportCode(options.imports,
                              "/* Imports for global scope */\n\n", true),
        servePath: options.importStubServePath
      });
    }
    return ret.concat(options.prelinkFiles);
  }

  var header = getHeader({
    imports: options.imports,
    packageVariables: options.packageVariables
  });

  var exported = _.pluck(_.filter(options.packageVariables, function (v) {
    return v.export;
  }), 'name');

  var footer = getFooter({
    exported: exported,
    name: options.name
  });

  var ret = [];
  _.each(options.prelinkFiles, function (file) {
    if (file.sourceMap) {
      if (options.includeSourceMapInstructions)
        header = SOURCE_MAP_INSTRUCTIONS_COMMENT + "\n\n" + header;

      // Bias the source map by the length of the header without
      // (fully) parsing and re-serializing it. (We used to do this
      // with the source-map library, but it was incredibly slow,
      // accounting for over half of bundling time.) It would be nice
      // if we could use "index maps" for this (the 'sections' key),
      // as that would let us avoid even JSON-parsing the source map,
      // but that doesn't seem to be supported by Firefox yet.
      if (header.charAt(header.length - 1) !== "\n")
        header += "\n"; // make sure it's a whole number of lines
      var headerLines = header.split('\n').length - 1;
      var sourceMapJson = JSON.parse(file.sourceMap);
      sourceMapJson.mappings = (new Array(headerLines + 1).join(';')) +
        sourceMapJson.mappings;
      ret.push({
        source: header + file.source + footer,
        servePath: file.servePath,
        sourceMap: JSON.stringify(sourceMapJson)
      });
    } else {
      ret.push({
        source: header + file.source + footer,
        servePath: file.servePath
      });
    }
  });

  return ret;
};

var getHeader = function (options) {
  var chunks = [];
  chunks.push("(function () {\n\n" );
  chunks.push(getImportCode(options.imports, "/* Imports */\n", false));
  if (!_.isEmpty(options.packageVariables)) {
    chunks.push("/* Package-scope variables */\n");
    chunks.push("var " + _.pluck(options.packageVariables, 'name').join(', ') +
                ";\n\n");
  }
  return chunks.join('');
};

var getImportCode = function (imports, header, omitvar) {
  var self = this;

  if (_.isEmpty(imports))
    return "";

  // Imports
  var scratch = {};
  _.each(imports, function (name, symbol) {
    scratch[symbol] = packageDot(name) + "." + symbol;
  });
  var tree = buildSymbolTree(scratch);

  // Generate output
  var buf = header;
  _.each(tree, function (node, key) {
    buf += (omitvar ? "" : "var " ) +
      key + " = " + writeSymbolTree(node) + ";\n";
  });
  buf += "\n";

  return buf;
};

var getFooter = function (options) {
  var chunks = [];

  if (options.name && options.exported) {
    chunks.push("\n\n/* Exports */\n");
    chunks.push("if (typeof Package === 'undefined') Package = {};\n");
    chunks.push(packageDot(options.name), " = ");

    // Even if there are no exports, we need to define Package.foo, because the
    // existence of Package.foo is how another package (eg, one that weakly
    // depends on foo) can tell if foo is loaded.
    if (_.isEmpty(options.exported)) {
      chunks.push("{};\n");
    } else {
      // A slightly overkill way to print out a properly indented version of
      // {Foo: Foo, Bar: Bar, Quux: Quux}. (This was less overkill back when
      // you could export dotted symbols.)
      var scratch = {};
      _.each(options.exported, function (symbol) {
        scratch[symbol] = symbol;
      });
      var exportTree = buildSymbolTree(scratch);
      chunks.push(writeSymbolTree(exportTree));
      chunks.push(";\n");
    }
  }

  chunks.push("\n})();\n");
  return chunks.join('');
};

var linker = module.exports = {
  prelink: prelink,
  link: link
};


// Given some functions, run them in every possible order.
//
// In simplest usage, takes one argument, an array of functions. Run
// those functions in every possible order. Or, if the first element
// of the array is an integer N, with the remaining elements being
// functions (N <= the number of functions), run every permutation of
// N functions from the array.
//
// Eg:
// try_all_permutations([A, B, C])
// => runs A, B, C; A, C, B; B, A, C; B, C, A; C, A, B; C, B, A
// (semicolons for clarity only)
//
// try_all_permutations([2, A, B, C])
// => runs A, B; A, C; B, A; B, C; C, A; C, B
//
// If more than one argument A_1, A_2 ... A_n is passed, each should
// be an array as described above. Compute the possible orderings O_1,
// O_2 ... O_n per above, and run the Cartesian product of the
// sets. (Except that unlike a proper Cartesian product, a set with
// zero elements will simply be ignored.)
//
// Eg:
// try_all_permutations([X], [A, B], [Y])
// => runs X, A, B, Y; X, B, A, Y
// try_all_permutations([X], [A, B], [], [Y])
// => same
//
// If a function is passed instead of an array, it will be treated as
// an array with one argument. In other words, these are the same:
// try_all_permutations([X], [A, B], [Y])
// try_all_permutations(X, [A, B], Y)

try_all_permutations = function () {
  var args = Array.prototype.slice.call(arguments);

  var current_set = 0;
  var chosen = [];

  var expand_next_set = function () {
    if (current_set === args.length) {
      _.each(chosen, function (f) { f(); });
    } else {
      var set = args[current_set];
      if (typeof set === "function")
        set = [set];

      current_set++;
      if (typeof set[0] === "number")
        pick(set[0], set.slice(1));
      else
        pick(set.length, set);
      current_set--;
    }
  };

  var pick = function (how_many, remaining) {
    if (how_many === 0)
      expand_next_set();
    else {
      for (var i = 0; i < remaining.length; i++) {
        chosen.push(remaining[i]);
        pick(how_many - 1,
             remaining.slice(0, i).concat(remaining.slice(i + 1)));
        chosen.pop();
      }
    }
  };

  expand_next_set();
};


Tinytest.add("environment - browser basics", function (test) {
  test.isTrue(Meteor.isClient);
  test.isFalse(Meteor.isServer);
  test.isFalse(Meteor.isCordova);
});


keep keep keep keep keep replace keep keep keep keep keep

To provide codes that are exactly compatible with all other Authenticator apps and services that implements TOTP, this package uses [node-2fa](https://www.npmjs.com/package/node-2fa) which works on top of [notp](https://github.com/guyht/notp), **that** implements TOTP ([RFC 6238](https://www.ietf.org/rfc/rfc6238.txt)) (the Authenticator standard), which is based on HOTP ([RFC 4226](https://www.ietf.org/rfc/rfc4226.txt)).

---
title: accounts-2fa
description: Documentation of Meteor's `accounts-2fa` package.
---

This package allows you to provide a way for your users to enable 2FA on their accounts, using an authenticator app such as Google Authenticator, or 1Password. When the user is logged in on your app, they will be able to generate a new QR code and read this code on the app they prefer. After that, they'll start receiving their codes. Then, they can finish enabling 2FA on your app, and every time they try to log in to your app, you can redirect them to a place where they can provide a code they received from the authenticator.

This package uses [node-2fa](https://www.npmjs.com/package/node-2fa) which works on top of [notp](https://github.com/guyht/notp), **that** implements TOTP ([RFC 6238](https://www.ietf.org/rfc/rfc6238.txt)) (the Authenticator standard), which is based on HOTP ([RFC 4226](https://www.ietf.org/rfc/rfc4226.txt)) to provide codes that are exactly compatible with all other Authenticator apps and services that use them.

> This package is meant to be used with [`accounts-password`](https://docs.meteor.com/api/passwords.html) or [`accounts-passwordless`](https://docs.meteor.com/packages/accounts-passwordless.html), so if you don't have either of those in your project, you'll need to add one of them. In the future, we want to enable the use of this package with other login methods, our oauth methods (Google, GitHub, etc...).

Activating 2FA

The first step, in order to enable 2FA, is to generate a QR code so that the user can scan it in an authenticator app and start receiving codes.

{% apibox "Accounts.generate2faActivationQrCode" "module":"accounts-base" %}

Receives an `appName` which is the name of your app that will show up when the user scans the QR code. Also, a callback called with a QR code in SVG format and QR secret on success
or a single `Error` argument on failure.

On success, this function will also add an object to the logged user's services object containing the QR secret:

```js
services: {
  ...
  twoFactorAuthentication: {
    secret: "***"
  }
}
```

Here it's an example on how to call this function:

```js
import { Buffer } from "buffer";
import { Accounts } from 'meteor/accounts-base';

--

const [qrCode, setQrCode] = useState(null);

--

<button
  onClick={() => {
    Accounts.generate2faActivationQrCode("My app name", (err, svg) => {
      if (err) {console.error("...", err);return;}
      /*
        the svg can be converted to base64, then be used like:
         <img
            width="200"
            src={`data:image/svg+xml;base64,${qrCode}`}
         />
      */
      setQrCode(Buffer.from(svg).toString('base64'));
    })
  }}
>
  Generate a new code
</button>
```

At this point, the 2FA won't be activated just yet. Now that the user has access to the codes generated by their authenticator app, you can call the function `Accounts.enableUser2fa`:

{% apibox "Accounts.enableUser2fa" "module":"accounts-base" %}

It should be called with a code that the users will receive from the authenticator app once they read the QR code. The callback is called with a single `Error` argument on failure. If the code provided is correct, a `type` will be added to the user's `twoFactorAuthentication` object and now 2FA is considered enabled:

```js
services: {
  ...
  twoFactorAuthentication: {
    type: "otp",
    secret: "***",
  }
}
```

To verify whether or not a user has 2FA enabled, you can call the function `Accounts.has2faEnabled`:

{% apibox "Accounts.has2faEnabled" "module":"accounts-base" %}

This function must be called when the user is logged in.

Disabling 2FA

To disable 2FA for a user use this method:

{% apibox "Accounts.disableUser2fa" "module":"accounts-base" %}

To call this function the user must be already logged in.

Log in with 2FA

Now that you have a way to allow your users to enable 2FA on their accounts, you can create a login flow based on that.

As said at the beginning of this guide, this package is currently working with two other packages: `accounts-password` and `accounts-passwordless`. Below there is an explanation on how to use this package with them.

Working with accounts-password

When calling the function `Meteor.loginWithPassword`, if the 2FA is enabled for the user, an error will be returned to the callback, so you can redirect the user to a place where they can provide a code.

As an example:

```js
<button
  onClick={() => {
    Meteor.loginWithPassword(username, password, error => {
      if (error) {
        if (error.error === 'no-2fa-code') {
            // send user to a page or show a component
            // where they can provide a 2FA code
            setShouldAskCode(true);
            return;
        }
        console.error("Error trying to log in (user without 2fa)", error);
      }
    });
  }
}>
  Login
</button>
```

If the 2FA is not enabled, the user will be logged in normally.

The function you will need to call now to allow the user to login is `Meteor.loginWithPasswordAnd2faCode`:

{% apibox "Meteor.loginWithPasswordAnd2faCode" %}

Now you will be able to receive a code from the user and this function will verify if the code is valid. If it is, the user will be logged in.

So the call of this function should look something like this:

```js
<button onClick={() => {
  Meteor.loginWithPasswordAnd2faCode(username, password, code, error => {
    if (error) {
      console.error("Error trying to log in (user with 2fa)", error);
    }
  })}
}>
  Validate and log in
</button>
```

Working with accounts-passwordless

Following the same logic from the previous package, if the 2FA is enabled, an error will be returned to the callback of the function [`Meteor.passwordlessLoginWithToken`](https://docs.meteor.com/packages/accounts-passwordless.html#Meteor-passwordlessLoginWithToken), then you can redirect the user to a place where they can provide a code.

Here is an example:

```js
<button
  onClick={() => {
    // logging in just with token
    Meteor.passwordlessLoginWithToken(
      email,
      token,
      error => {
        if (error) {
          if (error.error === 'no-2fa-code') {
            // send user to a page or show a component
            // where they can provide a 2FA code
            setShouldAskCode(true);
            return;
          }
          console.error('Error verifying token', error);
        }
      }
    );
  }}
>
  Validate token
</button>;
```

Now you can call the function `Meteor.passwordlessLoginWithTokenAnd2faCode` that will allow you to provide a selector, token, and 2FA code:

{% apibox "Meteor.passwordlessLoginWithTokenAnd2faCode" %}

How to integrate an Authentication Package with accounts-2fa

To integrate this package with any other existing Login method, it's necessary following two steps:

1 - For the client, create a new method from your current login method. So for example, from the method `Meteor.loginWithPassword` we created a new one called `Meteor.loginWithPasswordAnd2faCode`, and the only difference between them is that the latest one receives one additional parameter, the 2FA code, but we call the same function on the server side.

2 - For the server, inside the function that will log the user in, you verify if the function `Accounts._check2faEnabled` exists, and if yes, you call it providing the user you want to check if the 2FA is enabled, and if either of these statements are false, you proceed with the login flow. This function exists only when the package `accounts-2fa` is added to the project.

If both statements are true, and the login validation succeeds, you verify if a code was provided: if not, throw an error; if it was provided, verify if the code is valid by calling the function `Accounts._isTokenValid`. if `Accounts._isTokenValid` returns false, throw an error.

Here it's an example:

```js
  const result = validateLogin();
  if (
    !result.error &&
    Accounts._check2faEnabled?.(user)
  ) {
    if (!code) {
      Accounts._handleError('2FA code must be informed.');
    }
    if (
      !Accounts._isTokenValid(user.services.twoFactorAuthentication.secret, code)
    ) {
      Accounts._handleError('Invalid 2FA code.');
    }
  }

  return result;
```


ConstraintSolver = {};

var PV = PackageVersion;
var CS = ConstraintSolver;

////////// PackageAndVersion

// An ordered pair of (package, version).
CS.PackageAndVersion = function (pkg, version) {
  check(pkg, String);
  check(version, String);

  this.package = pkg;
  this.version = version;
};

// The string form of a PackageAndVersion is "package version",
// for example "foo 1.0.1".  The reason we don't use an "@" is
// it would look too much like a PackageConstraint.
CS.PackageAndVersion.prototype.toString = function () {
  return this.package + " " + this.version;
};

CS.PackageAndVersion.fromString = function (str) {
  var parts = str.split(' ');
  if (parts.length === 2 && parts[0] && parts[1]) {
    return new CS.PackageAndVersion(parts[0], parts[1]);
  } else {
    throw new Error("Malformed PackageAndVersion: " + str);
  }
};

////////// Dependency

// A Dependency consists of a PackageConstraint (like "foo@=1.2.3")
// and flags, like "isWeak".

CS.Dependency = function (packageConstraint, flags) {
  if (typeof packageConstraint !== 'string') {
    // this `if` is because Match.OneOf is really, really slow when it fails
    check(packageConstraint, Match.OneOf(PV.PackageConstraint, String));
  }
  if (typeof packageConstraint === 'string') {
    packageConstraint = PV.parsePackageConstraint(packageConstraint);
  }
  if (flags) {
    check(flags, Object);
  }

  this.packageConstraint = packageConstraint;
  this.isWeak = false;

  if (flags) {
    if (flags.isWeak) {
      this.isWeak = true;
    }
  }
};

// The string form of a Dependency is `?foo@1.0.0` for a weak
// reference to package "foo" with VersionConstraint "1.0.0".
CS.Dependency.prototype.toString = function () {
  var ret = this.packageConstraint.toString();
  if (this.isWeak) {
    ret = '?' + ret;
  }
  return ret;
};

CS.Dependency.fromString = function (str) {
  var isWeak = false;

  if (str.charAt(0) === '?') {
    isWeak = true;
    str = str.slice(1);
  }

  var flags = isWeak ? { isWeak: true } : null;

  return new CS.Dependency(str, flags);
};


# QA Notes
## Hot Code Push Reload

Run the leaderboard example, and click on one of the names.  Make a
change to the leaderboard.html file, see the client reload, and see
that the name is still selected.

## AUTOUPDATE_VERSION

Set the `AUTOUPDATE_VERSION` environment variable when running the
application:

    $ AUTOUPDATE_VERSION=abc meteor

Now when you make an HTML change, it won't appear in the client
automatically.  (Note the leader list flickers when the server
subscription restarts, but that's not a window reload).

Conversely, you can force a client reload (even without making any
client code changes) by restarting the server with a new value for
`AUTOUPDATE_VERSION`.

## No Client Reload on Server-only Change

Revert previous changes and run the example without setting
AUTOUPDATE_VERSION.

Note that it might look like the browser is reloading because the page
content in the leaderboard example will flicker when the server
restarts because the example is using autopublish, but that the window
won't actually be reloading.

In the browser console, assign a variable such as `a = true` so that
you can easily verify that the client hasn't reloaded.

In the leaderboard example directory, create the `server` directory
and add `foo.js`.  See in the browser console that `a` is still
defined, indicating the browser hasn't reloaded.

## Test with the appcache

Add the appcache package:

    $ meteor add appcache

And do the above tests again.

Note that if 1) AUTOUPDATE_VERSION is set so the client doesn't
automatically reload, 2) you make a client change, and 3) you manually
reload the browser page, you usually *won't* see the updated HTML the
*first* time you reload (unless the browser happened to check the app
cache manifest between steps 2 and 3).  This is normal browser app
cache behavior: the browser populates the app cache in the background,
so it doesn't wait for new files to download before displaying the web
page.

## Autoupdate.newClientAvailable

Undo previous changes made, such as by using `git checkout .` Reload
the client, which will cause the browser to stop using the app cache.

It's hard to see the `newClientAvailable` reactive variable when the
client automatically reloads.  Remove the `hot-code-push` package so you can
see the variable without having the client also reload.

    $ meteor remove meteor-base
    $ meteor add meteor webapp ddp autoupdate

Add to leaderboard.js:

    Template.leaderboard.helpers({
      available: function () {
        return Autoupdate.newClientAvailable().toString();
      }
    });

And add `{{available}}` to the leaderboard template in
leaderboard.html.

Initially you'll see `false`, and then when you make a change to the
leaderboard HTML you'll see the variable change to `true`.  (You won't
see the new HTML on the client because you disabled reload).

Amusingly, you can undo the addition you made to the HTML and the "new
client available" variable will go back to `false` (you now don't have
client code available on the server different than what's running in
the browser), because by default the client version is based on a hash
of the client files.

## DDP Version Negotiation Failure

A quick way to test DDP version negotiation failure is to force the
client to use the wrong DDP version.  At the top of
livedata_connection.js:

     var Connection = function (url, options) {
        var self = this;
    +   options.supportedDDPVersions = ['abc'];

You will see the client reload (in the hope that new client code will
be available that can successfully negotiation the DDP version).  Each
reload takes longer than the one before, using an exponential backoff.

If you remove the `options.supportedDDPVersions` line and allow the
client to connect (or manually reload the browser page so you don't
have to wait), this will reset the exponential backoff counter.

You can verify the counter was reset by adding the line back in a
second time, and you'll see the reload cycle start over again with
first reloading quickly, and then again taking longer between tries.


"use strict";

const assert = require("assert");
const { readFileSync } = require("fs");
const {
  join: pathJoin,
  normalize: pathNormalize,
} = require("path");
const { fetchURL } = require("./common.js");
const { Meteor } = require("meteor/meteor");
const hasOwn = Object.prototype.hasOwnProperty;

require("./security.js");

const client = require("./client.js");

Meteor.startup(() => {
  if (! Package.webapp) {
    // If the webapp package is not in use, there's no way for the
    // dynamic-import package to fetch dynamic modules, so we should
    // abandon the rest of the logic in this module.
    //
    // If api.use("webapp") appeared in dynamic-import/package.js, then
    // Package.webapp would always be defined here, of course, but that
    // would be a bad idea, because the dynamic-import package should not
    // single-handedly force a dependency on webapp if the program does
    // not otherwise need a web server (e.g., when the program is an
    // isopacket or build plugin instead of a web application).
    //
    // Note that the client.js module (imported above) still defines
    // Module.prototype.dynamicImport, which will work as long as no
    // modules need to be fetched.
    return;
  }

  Object.keys(dynamicImportInfo).forEach(setUpPlatform);

  Package.webapp.WebAppInternals.meteorInternalHandlers.use(
    fetchURL,
    middleware
  );
});

function setUpPlatform(platform) {
  const info = dynamicImportInfo[platform];

  if (info.dynamicRoot) {
    info.dynamicRoot = pathNormalize(info.dynamicRoot);
  }

  if (platform === "server") {
    client.setSecretKey(info.key = randomId(40));
  }
}

function randomId(n) {
  let s = "";
  while (s.length < n) {
    s += Math.random().toString(36).slice(2);
  }
  return s.slice(0, n);
}

function middleware(request, response) {
  // Allow dynamic import() requests from any origin.
  response.setHeader("Access-Control-Allow-Origin", "*");

  if (request.method === "OPTIONS") {
    const acrh = request.headers["access-control-request-headers"];
    response.setHeader('Allow', 'OPTIONS, POST');
    response.setHeader('Content-Length', '0');
    response.setHeader(
      "Access-Control-Allow-Headers",
      typeof acrh === "string" ? acrh : "*"
    );
    response.setHeader("Access-Control-Allow-Methods", "POST");
    response.end();

  } else if (request.method === "POST") {
    const chunks = [];
    request.on("data", chunk => chunks.push(chunk));
    request.on("end", () => {
      try {
        const tree = JSON.stringify(readTree(
          JSON.parse(Buffer.concat(chunks)),
          getPlatform(request)
        ), null, 2);

        response.writeHead(200, {
          "Content-Type": "application/json"
        });

        response.end(tree);

      } catch (e) {
        response.writeHead(400, {
          "Content-Type": "application/json"
        });

        response.end(JSON.stringify(
          Meteor.isDevelopment && e.message || "bad request"
        ));
      }
    });

  } else {
    const body = `method ${request.method} not allowed`;
    response.writeHead(405, {
      Allow: "OPTIONS, POST",
      'Content-Length': Buffer.byteLength(body),
      "Cache-Control": "no-cache"
    });
    response.end(body);
  }
}

function getPlatform(request) {
  // If the __dynamicImport request includes a secret key, and it matches
  // dynamicImportInfo[platform].key, use platform instead of the default
  // platform, web.browser.
  const secretKey = request.query.key;
  if (typeof secretKey === "string") {
    for (const p of Object.keys(dynamicImportInfo)) {
      if (secretKey === dynamicImportInfo[p].key) {
        return p;
      }
    }
  }

  return Package.webapp.WebApp.categorizeRequest(request).arch;
}

function readTree(tree, platform) {
  const pathParts = [];

  function walk(node) {
    if (! node) {
      return null;
    }

    if (typeof node !== "object") {
      return read(pathParts, platform);
    }

    let empty = true;

    Object.keys(node).forEach(name => {
      pathParts.push(name);
      const result = walk(node[name]);
      if (result === null) {
        // If the read function returns null, omit this module from the
        // resulting tree.
        delete node[name];
      } else {
        node[name] = result;
        empty = false;
      }
      assert.strictEqual(pathParts.pop(), name);
    });

    if (empty) {
      // If every recursive call to walk(node[name]) returned null,
      // remove this node from the resulting tree by returning null.
      return null;
    }

    return node;
  }

  return walk(tree);
}

function read(pathParts, platform) {
  const { dynamicRoot } = dynamicImportInfo[platform];
  const absPath = pathNormalize(pathJoin(
    dynamicRoot,
    pathJoin(...pathParts).replace(/:/g, "_")
  ));

  if (! absPath.startsWith(dynamicRoot)) {
    console.error("bad dynamic import path:", absPath);
    return null;
  }

  const cache = getCache(platform);
  if (hasOwn.call(cache, absPath)) {
    return cache[absPath];
  }

  try {
    return cache[absPath] = readFileSync(absPath, "utf8");
  } catch (e) {
    console.error(e.stack || e);
    return null;
  }
}

const cachesByPlatform = Object.create(null);
function getCache(platform) {
  return hasOwn.call(cachesByPlatform, platform)
    ? cachesByPlatform[platform]
    : cachesByPlatform[platform] = Object.create(null);
}

const { onMessage } = require("meteor/inter-process-messaging");

onMessage("client-refresh", () => {
  // The caches for the web.browser[.legacy] platforms need to be
  // discarded whenever a client-only refresh occurs, so the new client
  // bundle does not fetch stale module data from dynamic import(). This
  // message is sent by tools/runners/run-app.js and also consumed by the
  // autoupdate package.
  Object.keys(cachesByPlatform).forEach(platform => {
    delete cachesByPlatform[platform];
  });
});


{% apibox "Accounts.generate2faActivationQrCode" "module":"accounts-base" %}

Receives an `appName` which is the name of your app that will show up when the user scans the QR code. Also, a callback called with a QR code in SVG format and QR secret on success

or a single `Error` argument on failure.

On success, this function will also add an object to the logged user's services object containing the QR secret:

keep keep keep replace keep keep keep keep

Receives an `appName` which is the name of your app that will show up when the user scans the QR code. Also, a callback called, on success, with a QR code in SVG format, a QR secret, and the URI that can be used to activate the 2FA in an authenticator app,


# retry
[Source code of released version](https://github.com/meteor/meteor/tree/master/packages/retry) | [Source code of development version](https://github.com/meteor/meteor/tree/devel/packages/retry)
***

This is an internal Meteor package.


try {
  require("core-js/modules/es.object.get-own-property-descriptors");
} catch (e) {
  throw new Error([
    "The core-js npm package could not be found in your node_modules ",
    "directory. Please run the following command to install it:",
    "",
    "  meteor npm install --save core-js",
    ""
  ].join("\n"));
}

require("core-js/modules/es.object.is");
require("core-js/modules/es.function.name");
require("core-js/modules/es.number.is-finite");
require("core-js/modules/es.number.is-nan");
require("core-js/modules/es.array.flat");
require("core-js/modules/es.array.flat-map");
require("core-js/modules/es.object.from-entries");
require("core-js/modules/es.string.pad-start");
require("core-js/modules/es.string.pad-end");
require("core-js/modules/es.string.trim-start");
require("core-js/modules/es.string.trim-end");
require("core-js/modules/es.symbol.async-iterator");