Accounts.generate2faActivationQrCode("My app name", (err, svg) => {

if (err) {console.error("...", err);return;}

Accounts.generate2faActivationQrCode("My app name", (err, result) => {

import { Accounts } from 'meteor/accounts-base';
import twofactor from 'node-2fa';
import QRCode from 'qrcode-svg';
import { Meteor } from 'meteor/meteor';
import { check, Match } from 'meteor/check';

Accounts._check2faEnabled = user => {
  const { services: { twoFactorAuthentication } = {} } = user;
  return !!(
    twoFactorAuthentication &&
    twoFactorAuthentication.secret &&
    twoFactorAuthentication.type === 'otp'
  );
};

Accounts._is2faEnabledForUser = () => {
  const user = Meteor.user();
  if (!user) {
    throw new Meteor.Error('no-logged-user', 'No user logged in.');
  }
  return Accounts._check2faEnabled(user);
};

Accounts._generate2faToken = secret => twofactor.generateToken(secret);

Accounts._isTokenValid = (secret, code) => {
  if (!Meteor.isServer) {
    throw new Meteor.Error(
      400,
      'The function _isTokenValid can only be called on the server'
    );
  }
  const { delta } = twofactor.verifyToken(secret, code, 10) || {};
  return delta != null && delta >= 0;
};

Meteor.methods({
  generate2faActivationQrCode(appName) {
    check(appName, String);
    const user = Meteor.user();

    if (!user) {
      throw new Meteor.Error(
        400,
        'There must be a user logged in to generate the QR code.'
      );
    }

    if (Accounts._check2faEnabled(user)) {
      throw new Meteor.Error(
        '2fa-activated',
        'The 2FA is activated. You need to disable the 2FA first before trying to generate a new activation code.'
      );
    }

    const emails = user.emails || [];
    const { secret, uri } = twofactor.generateSecret({
      name: appName.trim(),
      account: user.username || emails[0]?.address || user._id,
    });
    const svg = new QRCode(uri).svg();

    Meteor.users.update(
      { _id: user._id },
      {
        $set: {
          'services.twoFactorAuthentication': {
            secret,
          },
        },
      }
    );

    return { svg, secret };
  },
  enableUser2fa(code) {
    check(code, String);
    const user = Meteor.user();

    if (!user) {
      throw new Meteor.Error(400, 'No user logged in.');
    }

    const {
      services: { twoFactorAuthentication },
    } = user;

    if (!twoFactorAuthentication || !twoFactorAuthentication.secret) {
      throw new Meteor.Error(
        500,
        'The user does not have a secret generated. You may have to call the function generateSvgCode first.'
      );
    }
    if (!Accounts._isTokenValid(twoFactorAuthentication.secret, code)) {
      throw new Meteor.Error(400, 'Invalid code.');
    }

    Meteor.users.update(
      { _id: user._id },
      {
        $set: {
          'services.twoFactorAuthentication': {
            ...twoFactorAuthentication,
            type: 'otp',
          },
        },
      }
    );
  },
  disableUser2fa() {
    const userId = Meteor.userId();

    if (!userId) {
      throw new Meteor.Error(400, 'No user logged in.');
    }

    Meteor.users.update(
      { _id: userId },
      {
        $unset: {
          'services.twoFactorAuthentication': 1,
        },
      }
    );
  },
  has2faEnabled() {
    return Accounts._is2faEnabledForUser();
  },
});

Accounts.addAutopublishFields({
  forLoggedInUser: ['services.twoFactorAuthentication.type'],
});


(function(){

/////////////////////////////////////////////////////////////////////////
//                                                                     //
// mobileapp.js                                                        //
//                                                                     //
/////////////////////////////////////////////////////////////////////////
                                                                       //
if (Meteor.isClient) {                                                 // 1
  // counter starts at 0                                               //
  Session.setDefault('counter', 0);                                    // 3
                                                                       //
  Template.hello.helpers({                                             // 5
    counter: function () {                                             // 6
      return Session.get('counter');                                   // 7
    }                                                                  //
  });                                                                  //
                                                                       //
  Template.hello.events({                                              // 11
    'click button': function () {                                      // 12
      // increment the counter when button is clicked                  //
      Session.set('counter', Session.get('counter') + 1);              // 14
    }                                                                  //
  });                                                                  //
}                                                                      //
                                                                       //
if (Meteor.isServer) {                                                 // 19
  Meteor.startup(function () {                                         // 20
    // code to run on server at startup                                //
  });                                                                  //
}                                                                      //
/////////////////////////////////////////////////////////////////////////

}).call(this);


---
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;
```


dev-bundle-bin-commands

echo "This script has an exit status" && exit 1

echo "This script will exit normally" && exit 0


// This app doesn't do anything, we just check versions


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`:

This method can fail throwing the following error:

* "The 2FA is activated. You need to disable the 2FA first before trying to generate a new activation code [2fa-activated]" if trying to generate an activation when the user already have 2FA enabled.


# This file contains a token that is unique to your project.
# Check it into your repository along with the rest of this directory.
# It can be used for purposes such as:
#   - ensuring you don't accidentally deploy one app on top of another
#   - providing package authors with aggregated statistics

fgbyyt1b3uxw5u734ug


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

Meteor's user account system. This package implements the basic functions necessary for user accounts and lets other packages register login services. Some of these services are in the following packages:

- `accounts-password`
- `accounts-facebook`
- `accounts-google`
- `accounts-github`
- `accounts-twitter`
- `accounts-meetup`
- `accounts-weibo`

There are also login services available in community packages.

For more information, see the [Meteor docs](http://docs.meteor.com/#accounts_api) and the Meteor Accounts [project page](https://www.meteor.com/accounts).


Package.describe({
  version: "1.0.0",
  summary: "depend on and watch a plugin"
});

Package.onUse(function (api) {
  api.addFiles('foo.js');
  api.use('contains-plugin');
});


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.

This method can fail throwing one of the following errors:

* "2FA code must be informed [no-2fa-code]" if a 2FA code was not provided.

* "Invalid 2FA code [invalid-2fa-code]" if the provided 2FA code is invalid.


<template name="App_home">
  {{> hello}}
  {{> info}}
</template>


$el2-style = solid
@import "../imports/dotdot.styl"


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:


Package.describe({
  name: "~package-name~", // replaced via `Sandbox.prototype.createPackage`
  summary: 'This is a test package',
  version: '0.9.9'
});


Package.describe({
  summary: "Used by the coffeescript package's tests",
  version: "1.2.0"
});

Package.onUse(function (api) {
  api.use('coffeescript@2.4.1', ['client', 'server']);
  api.export('COFFEESCRIPT_EXPORTED');
  api.export('COFFEESCRIPT_EXPORTED_ONE_MORE');
  api.export('COFFEESCRIPT_EXPORTED_WITH_BACKTICKS');
  api.addFiles("exporting.coffee", ['client', 'server']);
});


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.

keep keep keep keep replace keep keep keep keep

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 object 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.


// Meteor._localStorage is not an ideal name, but we can change it later.

// Let's test to make sure that localStorage actually works. For example, in
// Safari with private browsing on, window.localStorage exists but actually
// trying to use it throws.
// Accessing window.localStorage can also immediately throw an error in IE (#1291).

var hasOwn = Object.prototype.hasOwnProperty;
var key = '_localstorage_test_' + Random.id();
var retrieved;
var storage;

try {
  storage = global.localStorage;

  if (storage) {
    storage.setItem(key, key);
    retrieved = storage.getItem(key);
    storage.removeItem(key);
  }
} catch (ignored) {}

if (key === retrieved) {
  if (Meteor.isServer) {
    Meteor._localStorage = storage;
  } else {
    // Some browsers (e.g. IE11) don't properly handle attempts to change
    // window.localStorage methods. By using proxy methods to expose
    // window.localStorage functionality, developers can change the
    // behavior of Meteor._localStorage methods without breaking
    // window.localStorage.
    ["getItem",
     "setItem",
     "removeItem",
    ].forEach(function (name) {
      this[name] = function () {
        return storage[name].apply(storage, arguments);
      };
    }, Meteor._localStorage = {});
  }
}

if (! Meteor._localStorage) {
  if (Meteor.isClient) {
    Meteor._debug(
      "You are running a browser with no localStorage or userData "
        + "support. Logging in from one tab will not cause another "
        + "tab to be logged in.");
  }

  Meteor._localStorage = Object.create({
    setItem: function (key, val) {
      this[key] = val;
    },

    removeItem: function (key) {
      delete this[key];
    },

    getItem: function (key) {
      return hasOwn.call(this, key) ? this[key] : null;
    }
  });
}


import { Tinytest } from "meteor/tinytest";

Tinytest.add('extra-package-1 - example test', function (test) {
  test.equal(true, true);
});


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

This is one of a family of packages that can be used to easily
configure an app's browser-side security policies. The documentation
is in the
[browser-policy](https://atmospherejs.com/meteor/browser-policy)
package.


* @summary Generates a svg QR code and save secret on user

* @param {String} appName It's the name of your app that will show up when the user scans the QR code.

*   Called with a QR code in SVG format on success, or with a single `Error` argument

Accounts.generate2faActivationQrCode = (appName, callback) => {

keep keep keep keep keep replace replace keep keep keep keep keep

*   Called with a single `Error` argument on failure.

*   Or, on success, called with an object containing the QR code in SVG format (svg),

*   the QR secret (secret), and the URI so the user can manually activate the 2FA without reading the QR code (uri).


import {Parser} from "./state"
import {SCOPE_VAR, SCOPE_FUNCTION, SCOPE_TOP, SCOPE_ARROW, SCOPE_SIMPLE_CATCH, BIND_LEXICAL, BIND_SIMPLE_CATCH, BIND_FUNCTION} from "./scopeflags"

const pp = Parser.prototype

class Scope {
  constructor(flags) {
    this.flags = flags
    // A list of var-declared names in the current lexical scope
    this.var = []
    // A list of lexically-declared names in the current lexical scope
    this.lexical = []
    // A list of lexically-declared FunctionDeclaration names in the current lexical scope
    this.functions = []
  }
}

// The functions in this module keep track of declared variables in the current scope in order to detect duplicate variable names.

pp.enterScope = function(flags) {
  this.scopeStack.push(new Scope(flags))
}

pp.exitScope = function() {
  this.scopeStack.pop()
}

// The spec says:
// > At the top level of a function, or script, function declarations are
// > treated like var declarations rather than like lexical declarations.
pp.treatFunctionsAsVarInScope = function(scope) {
  return (scope.flags & SCOPE_FUNCTION) || !this.inModule && (scope.flags & SCOPE_TOP)
}

pp.declareName = function(name, bindingType, pos) {
  let redeclared = false
  if (bindingType === BIND_LEXICAL) {
    const scope = this.currentScope()
    redeclared = scope.lexical.indexOf(name) > -1 || scope.functions.indexOf(name) > -1 || scope.var.indexOf(name) > -1
    scope.lexical.push(name)
    if (this.inModule && (scope.flags & SCOPE_TOP))
      delete this.undefinedExports[name]
  } else if (bindingType === BIND_SIMPLE_CATCH) {
    const scope = this.currentScope()
    scope.lexical.push(name)
  } else if (bindingType === BIND_FUNCTION) {
    const scope = this.currentScope()
    if (this.treatFunctionsAsVar)
      redeclared = scope.lexical.indexOf(name) > -1
    else
      redeclared = scope.lexical.indexOf(name) > -1 || scope.var.indexOf(name) > -1
    scope.functions.push(name)
  } else {
    for (let i = this.scopeStack.length - 1; i >= 0; --i) {
      const scope = this.scopeStack[i]
      if (scope.lexical.indexOf(name) > -1 && !((scope.flags & SCOPE_SIMPLE_CATCH) && scope.lexical[0] === name) ||
          !this.treatFunctionsAsVarInScope(scope) && scope.functions.indexOf(name) > -1) {
        redeclared = true
        break
      }
      scope.var.push(name)
      if (this.inModule && (scope.flags & SCOPE_TOP))
        delete this.undefinedExports[name]
      if (scope.flags & SCOPE_VAR) break
    }
  }
  if (redeclared) this.raiseRecoverable(pos, `Identifier '${name}' has already been declared`)
}

pp.checkLocalExport = function(id) {
  // scope.functions must be empty as Module code is always strict.
  if (this.scopeStack[0].lexical.indexOf(id.name) === -1 &&
      this.scopeStack[0].var.indexOf(id.name) === -1) {
    this.undefinedExports[id.name] = id
  }
}

pp.currentScope = function() {
  return this.scopeStack[this.scopeStack.length - 1]
}

pp.currentVarScope = function() {
  for (let i = this.scopeStack.length - 1;; i--) {
    let scope = this.scopeStack[i]
    if (scope.flags & SCOPE_VAR) return scope
  }
}

// Could be useful for `this`, `new.target`, `super()`, `super.property`, and `super[property]`.
pp.currentThisScope = function() {
  for (let i = this.scopeStack.length - 1;; i--) {
    let scope = this.scopeStack[i]
    if (scope.flags & SCOPE_VAR && !(scope.flags & SCOPE_ARROW)) return scope
  }
}


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

var release = exports;

var Release = function (options) {
  var self = this;

  // If an actual, proper, "released" release, the name of the
  // release, eg, "METEOR@1.0". If not a proper release, null.
  self.name = options.name;

  if (self.name === null) {
    // Running from checkout.
    self._manifest = null;
  } else {
    // Running a proper release
    self._manifest = options.manifest;
  }
};

Object.assign(Release.prototype, {
  // True if an actual, proper, "released" release. If so, this.name
  // will have the name of the release, eg, "1.0".
  isProperRelease: function () {
    return this.name !== null;
  },

  // True if this "release" is actually a checkout on disk. It is
  // defined by the packages in the checkout rather than by a
  // manifest. this.name will be null.
  isCheckout: function () {
    return this.name === null;
  },

  isRecommended: function () {
    return this._manifest.recommended;
  },

  getReleaseTrack: function () {
    var self = this;
    if (! self.isProperRelease()) {
      throw new Error("not a proper release?");
    }
    return self.name.split('@')[0];
  },

  getReleaseVersion: function () {
    var self = this;
    if (! self.isProperRelease()) {
      throw new Error("not a proper release?");
    }
    return self.name.split('@')[1];
  },

  // Return the package name for the command-line tools that this release
  // uses. Valid only for proper releases.
  getToolsPackage: function () {
    var self = this;

    if (! self.isProperRelease()) {
      throw new Error("not a proper release?");
    }
    // XXX validate
    return self._manifest.tool.split('@')[0];
  },

  // Return the version of the command-line tools that this release
  // uses. Valid only for proper releases.
  getToolsVersion: function () {
    var self = this;

    if (! self.isProperRelease()) {
      throw new Error("not a proper release?");
    }
    // XXX validate
    return self._manifest.tool.split('@')[1];
  },

  // Return the package name and version of the command-line tools that this
  // release uses. Valid only for proper releases.
  getToolsPackageAtVersion: function () {
    var self = this;

    if (! self.isProperRelease()) {
      throw new Error("not a proper release?");
    }
    return self._manifest.tool;
  },

  // Return the tool that we are using. If this is a proper release, return the
  // tool package listed in the manifest, otherwise return the version of the
  // meteor-tool package in checkout.
  getCurrentToolsVersion: function () {
    var self = this;

    if (release.current.name) {
      return self._manifest.tool;
    } else {
      return "meteor-tool@CHECKOUT";
    }
  },

  // Return a list of the upgraders (project migrations) for this
  // release, an (ordered!) array of strings. Valid only for proper
  // releases.
  getUpgraders: function () {
    var self = this;

    if (! self.isProperRelease()) {
      throw new Error("not a proper release?");
    }
    return self._manifest.upgraders || [];
  },

  getPackages: function () {
    var self = this;

    if (! self.isProperRelease()) {
      throw new Error("not a proper release?");
    }
    return self._manifest.packages;
  },

  getCatalogReleaseData: function () {
    var self = this;
    if (! self.isProperRelease()) {
      throw new Error("not a proper release?");
    }
    return self._manifest;
  },

  getDisplayName: function (options) {
    var self = this;
    const catalogUtils = require('./catalog/catalog-utils.js');
    return catalogUtils.displayRelease(
      self.getReleaseTrack(),
      self.getReleaseVersion(),
      options);
  }
});

// The current release. Once set, this does not change for the
// lifetime of the process.
//
// It is possible that we don't have a release. Currently this only
// comes up in one case: an app was created with a checkout version of
// Meteor, and then run with a release version of Meteor. In this case
// release.current will be null. (It will also be null during startup,
// until setRelease has been called.)
//
// (If you want to change the current release, you have to
// springboard, the same as if you want to change the current tools
// version. Besides being simpler to reason about, this helps to
// prepare us for a future where the 'meteor' tool itself is a Meteor
// app, running against a particular Meteor release.)
release.current = null;

// True if we are using release.current because we were forced to do that by the
// '--release' command line option or via throwing SpringboardToLatestRelease,
// else false. (It is true anytime --release was passed, even if it's the same
// release we would have used anyway. It is false anytime the current release is
// a checkin.) null if release.current is null.
release.forced = null;

// True if the release was explicitly specified by the user with the --release
// flag. Unlike release.forced, this is false when the release is overridden via
// SpringboardToLatestRelease.
release.explicit = null;

// True if release.current is the release we'd use if we wanted to run the app
// in the current project. (taking into account release.forced and whether we're
// currently running from a checkout).
release.usingRightReleaseForApp = function (projectContext) {
  if (release.current === null) {
    throw new Error("no release?");
  }

  if (! files.usesWarehouse() || release.forced) {
    return true;
  }

  return release.current.name === projectContext.releaseFile.fullReleaseName;
};

// Return the name of the latest release that is downloaded and ready
// for use. May not be called when running from a checkout.
// 'track' is optional (it defaults to the default track).
release.latestKnown = function (track) {
  if (! files.usesWarehouse()) {
    throw new Error("called from checkout?");
  }
  // For self-test only.
  if (process.env.METEOR_TEST_LATEST_RELEASE) {
    return process.env.METEOR_TEST_LATEST_RELEASE;
  }

  var defaultRelease = catalog.official.getDefaultReleaseVersion(track);

  if (!defaultRelease) {
    return null;
  }
  return defaultRelease.track + '@' + defaultRelease.version;
};

// Load a release and return it as a Release object without setting
// release.current to that release. Unlike release.setCurrent(), this
// may be called as many times as you like.
//
// This will fetch the release from the server if it isn't cached
// locally. If that happens it will print progress messages.
//
// Arguments:
// - name: release name to use. Or pass 'null' to use a checkout
// - options:
//   - quiet: if the release has to be downloaded, don't print
//     progress messages.
//
// Throws:
// - files.OfflineError if it was not possible to load the
//   release because it's not locally cached and we're not online.
// - warehouse.NoSuchReleaseError if no release called 'name' exists
//   in the world (confirmed with server).
release.load = function (name, options) {
  options = options || {};

  if (! name) {
    return new Release({ name: null });
  }

  var parts = name.split('@');
  if (parts.length > 2) {
    // XXX #Pre090 better error handling
    throw Error("too many @s in release name");
  }
  var track, version;
  if (parts.length == 2) {
    track = parts[0];
    version = parts[1];
  } else {
    track = catalog.DEFAULT_TRACK;
    version = parts[0];
    name = track + '@' + version;
  }

  var releaseVersion = catalog.official.getReleaseVersion(track, version);
  if (releaseVersion === null) {
    throw new release.NoSuchReleaseError;
  }

  return new Release({
    name: name,
    manifest: releaseVersion  // XXX rename from manifest?
  });
};

// Called by the startup code to set release.current. May only be
// called once.
//
// - releaseObject: a Release as returned from release.load()
// - forced: true if the chosen release was forced from the command
//   line (by the user or by the update springboard).
// - explicit: true if the release was specifically requested by the user.
release.setCurrent = function (releaseObject, forced, explicit) {
  if (release.current) {
    throw new Error("release set twice?");
  }

  release.current = releaseObject;
  release.forced = !! forced;
  release.explicit = !! explicit;
};

// An exception meaning that you asked for a release that doesn't exist in the
// new packaging world.  (It may still exist in the pre-0.9.0 packaging world.)
release.NoSuchReleaseError = function () {
};


'The function _isTokenValid can only be called on the server'

const { delta } = twofactor.verifyToken(secret, code, 10) || {};

keep keep keep keep keep replace keep keep keep

// we are using != instead of !==, which means "undefined != null" and "null != null" are both false,

// so we don't need to check delta !== undefined


// A MethodInvoker manages sending a method to the server and calling the user's
// callbacks. On construction, it registers itself in the connection's
// _methodInvokers map; it removes itself once the method is fully finished and
// the callback is invoked. This occurs when it has both received a result,
// and the data written by it is fully visible.
export default class MethodInvoker {
  constructor(options) {
    // Public (within this file) fields.
    this.methodId = options.methodId;
    this.sentMessage = false;

    this._callback = options.callback;
    this._connection = options.connection;
    this._message = options.message;
    this._onResultReceived = options.onResultReceived || (() => {});
    this._wait = options.wait;
    this.noRetry = options.noRetry;
    this._methodResult = null;
    this._dataVisible = false;

    // Register with the connection.
    this._connection._methodInvokers[this.methodId] = this;
  }
  // Sends the method message to the server. May be called additional times if
  // we lose the connection and reconnect before receiving a result.
  sendMessage() {
    // This function is called before sending a method (including resending on
    // reconnect). We should only (re)send methods where we don't already have a
    // result!
    if (this.gotResult())
      throw new Error('sendingMethod is called on method with result');

    // If we're re-sending it, it doesn't matter if data was written the first
    // time.
    this._dataVisible = false;
    this.sentMessage = true;

    // If this is a wait method, make all data messages be buffered until it is
    // done.
    if (this._wait)
      this._connection._methodsBlockingQuiescence[this.methodId] = true;

    // Actually send the message.
    this._connection._send(this._message);
  }
  // Invoke the callback, if we have both a result and know that all data has
  // been written to the local cache.
  _maybeInvokeCallback() {
    if (this._methodResult && this._dataVisible) {
      // Call the callback. (This won't throw: the callback was wrapped with
      // bindEnvironment.)
      this._callback(this._methodResult[0], this._methodResult[1]);

      // Forget about this method.
      delete this._connection._methodInvokers[this.methodId];

      // Let the connection know that this method is finished, so it can try to
      // move on to the next block of methods.
      this._connection._outstandingMethodFinished();
    }
  }
  // Call with the result of the method from the server. Only may be called
  // once; once it is called, you should not call sendMessage again.
  // If the user provided an onResultReceived callback, call it immediately.
  // Then invoke the main callback if data is also visible.
  receiveResult(err, result) {
    if (this.gotResult())
      throw new Error('Methods should only receive results once');
    this._methodResult = [err, result];
    this._onResultReceived(err, result);
    this._maybeInvokeCallback();
  }
  // Call this when all data written by the method is visible. This means that
  // the method has returns its "data is done" message *AND* all server
  // documents that are buffered at that time have been written to the local
  // cache. Invokes the main callback if the result has been received.
  dataVisible() {
    this._dataVisible = true;
    this._maybeInvokeCallback();
  }
  // True if receiveResult has been called.
  gotResult() {
    return !!this._methodResult;
  }
}


// This exports object was created in pre.js.  Now copy the `_` object from it
// into the package-scope variable `_`, which will get exported.
_ = exports._;


},
},
}
);
return { svg, secret };
},
enableUser2fa(code) {
check(code, String);
const user = Meteor.user();

keep keep keep keep keep replace keep keep keep keep


# 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.


import selftest from '../tool-testing/selftest.js';
import files from '../fs/files';
import { installNpmModule } from '../isobuild/meteor-npm.js';

const Sandbox = selftest.Sandbox;

const MONGO_LISTENING =
  { stdout: " [initandlisten] waiting for connections on port" };

selftest.define("npm", ["net"], () => {
  const s = new Sandbox({ fakeMongo: true });
  let run;

  s.createApp("npmtestapp", "npmtest", { dontPrepareApp: true });
  s.cd("npmtestapp");

  // Ensure that we don't lose the executable bits of npm modules.
  // Regression test for https://github.com/meteor/meteor/pull/1808
  // Before this fix, the module would work on the first execution but not on a
  // subsequent one.
  [1, 2].forEach(i => {
    run = s.run("--once", "--raw-logs");
    run.tellMongo(MONGO_LISTENING);
    if (i === 1) {
      run.waitSecs(30);
      // use match instead of read because on a built release we can
      // also get an update message here.
      run.match(
        "npm-test: updating npm dependencies -- meteor-test-executable...\n");
    }
    run.waitSecs(15);
    run.match("null; From shell script\n");
    run.expectExit(0);
  });
});

function testThatNpmInstallThrows(name, version, regexMatcher) {
  const tmpDir = files.convertToOSPath(files.mkdtemp());
  let didThrow = false;
  try {
    installNpmModule(name, version, tmpDir);
  } catch (err) {
    didThrow = true;
    selftest.expectTrue(regexMatcher.test(err.message));
  }
  selftest.expectTrue(didThrow);
}

selftest.define("npm - install - messages - error installing package", ["net"], () => {
  // the 'error-prone' npm intentionally errors in the preinstall script.
  testThatNpmInstallThrows("error-prone", "1.0.0",
    /couldn't install npm package error-prone@1.0.0/);
});

selftest.define("npm - install - messages - npm doesn't exist", ["net"], () => {
  // this test is obviously prone to sabotage.
  testThatNpmInstallThrows("non-existant-package-gggg", "100.0.0",
    /no npm package named 'non-existant-package-gggg' in the npm registry/);
});

selftest.define("npm - install - messages - npm version doesn't exist", ["net"], () => {
  // the 'cost-of-modules' npm really exists but hopefully never this minor ver.
  testThatNpmInstallThrows("cost-of-modules", "0.999.2",
    /cost-of-modules version 0.999.2 is not available in the npm registry/);
});


Accounts._handleError('Invalid 2FA code', true, 'invalid-2fa-code');

import { Accounts } from 'meteor/accounts-base';
import twofactor from 'node-2fa';
import QRCode from 'qrcode-svg';
import { Meteor } from 'meteor/meteor';
import { check, Match } from 'meteor/check';

Accounts._check2faEnabled = user => {
  const { services: { twoFactorAuthentication } = {} } = user;
  return !!(
    twoFactorAuthentication &&
    twoFactorAuthentication.secret &&
    twoFactorAuthentication.type === 'otp'
  );
};

Accounts._is2faEnabledForUser = () => {
  const user = Meteor.user();
  if (!user) {
    throw new Meteor.Error('no-logged-user', 'No user logged in.');
  }
  return Accounts._check2faEnabled(user);
};

Accounts._generate2faToken = secret => twofactor.generateToken(secret);

Accounts._isTokenValid = (secret, code) => {
  if (!Meteor.isServer) {
    throw new Meteor.Error(
      400,
      'The function _isTokenValid can only be called on the server'
    );
  }
  const { delta } = twofactor.verifyToken(secret, code, 10) || {};
  return delta != null && delta >= 0;
};

Meteor.methods({
  generate2faActivationQrCode(appName) {
    check(appName, String);
    const user = Meteor.user();

    if (!user) {
      throw new Meteor.Error(
        400,
        'There must be a user logged in to generate the QR code.'
      );
    }

    if (Accounts._check2faEnabled(user)) {
      throw new Meteor.Error(
        '2fa-activated',
        'The 2FA is activated. You need to disable the 2FA first before trying to generate a new activation code.'
      );
    }

    const emails = user.emails || [];
    const { secret, uri } = twofactor.generateSecret({
      name: appName.trim(),
      account: user.username || emails[0]?.address || user._id,
    });
    const svg = new QRCode(uri).svg();

    Meteor.users.update(
      { _id: user._id },
      {
        $set: {
          'services.twoFactorAuthentication': {
            secret,
          },
        },
      }
    );

    return { svg, secret };
  },
  enableUser2fa(code) {
    check(code, String);
    const user = Meteor.user();

    if (!user) {
      throw new Meteor.Error(400, 'No user logged in.');
    }

    const {
      services: { twoFactorAuthentication },
    } = user;

    if (!twoFactorAuthentication || !twoFactorAuthentication.secret) {
      throw new Meteor.Error(
        500,
        'The user does not have a secret generated. You may have to call the function generateSvgCode first.'
      );
    }
    if (!Accounts._isTokenValid(twoFactorAuthentication.secret, code)) {
      throw new Meteor.Error(400, 'Invalid code.');
    }

    Meteor.users.update(
      { _id: user._id },
      {
        $set: {
          'services.twoFactorAuthentication': {
            ...twoFactorAuthentication,
            type: 'otp',
          },
        },
      }
    );
  },
  disableUser2fa() {
    const userId = Meteor.userId();

    if (!userId) {
      throw new Meteor.Error(400, 'No user logged in.');
    }

    Meteor.users.update(
      { _id: userId },
      {
        $unset: {
          'services.twoFactorAuthentication': 1,
        },
      }
    );
  },
  has2faEnabled() {
    return Accounts._is2faEnabledForUser();
  },
});

Accounts.addAutopublishFields({
  forLoggedInUser: ['services.twoFactorAuthentication.type'],
});


body {
  height: 100vh;
  width: 100vw;
}

.test-in-browser {
  display: flex;
  height: 100%;
  flex-direction: column;
}

.test-results {
  flex: 1;
  overflow: auto;
}

#testProgressBar {
  flex: 1;
  max-width: 400px;
  position: relative;
}

.header {
  font-family: Arial, sans-serif;
  font-size: 24px;
  padding-bottom: 4px;
}

.header.in-progress {
  color: #ccc;
}

.header.pass {
  color: #090; /* green */
}

.header.fail {
  color: #900; /* red */
  font-weight: bold;
}

.header .time {
  color: #666;
  font-size: 14px;
}

.test_table {
  font-family: Arial, sans-serif;
  font-size: 16px;
}

.test_table .group {
  border-left: 1px solid #ddd;
}

.test_table .group .group {
  margin-left: 20px;
}

.test_table .test {
  margin-left: 20px;
}

.test_table .testname {
  margin-left: 200px;
  line-height: 24px;
  vertical-align: middle;
  text-decoration: underline;
  cursor: pointer;
}

.test_table .groupname {
  font-weight: bold;
  background: #ddd;
  padding-left: 5px;
  line-height: 24px;
  vertical-align: middle;
  font-size: 16px;
}

.test_table .event {
  margin-left: 20px;
  font-size: 14px;
  border-left: 2px solid #def;
  padding: 4px;
  position: relative;
}

.test_table .test .testrow { position: relative; overflow: hidden; /*hasLayout*/ }

.test_table .running .testname { color: #66c; /* blue */ }
.test_table .failed .testname { color: #900; /* red */ }
.test_table .succeeded .testname { color: #090; /* green */ }

.test_table .teststatus {
  position: absolute;
  height: 100%;
  width: 100px;
  left: 0px;
  top: 0;
  text-align: center;
  line-height: 24px;
  vertical-align: middle;
}

.test_table .testtime {
  position: absolute;
  height: 100%;
  width: 75px;
  margin-right: 5px;
  left: 100px;
  top: 0;
  text-align: right;
  line-height: 24px;
  vertical-align: middle;
  font-size: 14px;
  color: #666;
}

.test_table .succeeded .teststatus { color: #090; /* green */  background: #cfc; }
.test_table .failed .teststatus { color: #900; /* red */  background: #fcc; }
.test_table .running .teststatus { color: #ccc; }

.test_table .event .expected_fail { color: #600; }
.test_table .event .fail { color: #d00; }
.test_table .event .exception { color: #d00; }
.test_table .event .nodata { color: #ccc; font-style: italic; }

.test_table .event .xtimes, .test_table .event .failkey { font-weight: bold; }

.test_table .event .debug {
    display: none;
}

.test_table .event:hover .debug {
    display: inline;
    color: gray;
    text-decoration: underline;
    cursor: pointer;
}

.in-progress {
    position: absolute;
    left: 10px;
    z-index: 2;
}

.string_equal {
  line-height: 1.2;
  margin-left: 30px;
}
.string_equal ins {
  text-decoration: none;
}
.string_equal_expected ins {
  background: #ed0;
}
.string_equal_actual ins {
  color: #d00;
  background: #fcc;
}

#current-client-test {
  color: #ccc;
  margin-right: 15px;
}

.failedTests {
  color: #900; /* red */
}


Tinytest.add("diff-sequence - diff changes ordering", function (test) {
  var makeDocs = function (ids) {
    return _.map(ids, function (id) { return {_id: id};});
  };
  var testMutation = function (a, b) {
    var aa = makeDocs(a);
    var bb = makeDocs(b);
    var aaCopy = EJSON.clone(aa);
    DiffSequence.diffQueryOrderedChanges(aa, bb, {

      addedBefore: function (id, doc, before) {
        if (before === null) {
          aaCopy.push( _.extend({_id: id}, doc));
          return;
        }
        for (var i = 0; i < aaCopy.length; i++) {
          if (aaCopy[i]._id === before) {
            aaCopy.splice(i, 0, _.extend({_id: id}, doc));
            return;
          }
        }
      },
      movedBefore: function (id, before) {
        var found;
        for (var i = 0; i < aaCopy.length; i++) {
          if (aaCopy[i]._id === id) {
            found = aaCopy[i];
            aaCopy.splice(i, 1);
          }
        }
        if (before === null) {
          aaCopy.push( _.extend({_id: id}, found));
          return;
        }
        for (i = 0; i < aaCopy.length; i++) {
          if (aaCopy[i]._id === before) {
            aaCopy.splice(i, 0, _.extend({_id: id}, found));
            return;
          }
        }
      },
      removed: function (id) {
        var found;
        for (var i = 0; i < aaCopy.length; i++) {
          if (aaCopy[i]._id === id) {
            found = aaCopy[i];
            aaCopy.splice(i, 1);
          }
        }
      }
    });
    test.equal(aaCopy, bb);
  };

  var testBothWays = function (a, b) {
    testMutation(a, b);
    testMutation(b, a);
  };

  testBothWays(["a", "b", "c"], ["c", "b", "a"]);
  testBothWays(["a", "b", "c"], []);
  testBothWays(["a", "b", "c"], ["e","f"]);
  testBothWays(["a", "b", "c", "d"], ["c", "b", "a"]);
  testBothWays(['A','B','C','D','E','F','G','H','I'],
               ['A','B','F','G','C','D','I','L','M','N','H']);
  testBothWays(['A','B','C','D','E','F','G','H','I'],['A','B','C','D','F','G','H','E','I']);
});

Tinytest.add("diff-sequence - diff", function (test) {

  // test correctness

  var diffTest = function(origLen, newOldIdx) {
    var oldResults = new Array(origLen);
    for (var i = 1; i <= origLen; i++)
      oldResults[i-1] = {_id: i};

    var newResults = _.map(newOldIdx, function(n) {
      var doc = {_id: Math.abs(n)};
      if (n < 0)
        doc.changed = true;
      return doc;
    });
    var find = function (arr, id) {
      for (var i = 0; i < arr.length; i++) {
        if (EJSON.equals(arr[i]._id, id))
          return i;
      }
      return -1;
    };

    var results = _.clone(oldResults);
    var observer = {
      addedBefore: function(id, fields, before) {
        var before_idx;
        if (before === null)
          before_idx = results.length;
        else
          before_idx = find (results, before);
        var doc = _.extend({_id: id}, fields);
        test.isFalse(before_idx < 0 || before_idx > results.length);
        results.splice(before_idx, 0, doc);
      },
      removed: function(id) {
        var at_idx = find (results, id);
        test.isFalse(at_idx < 0 || at_idx >= results.length);
        results.splice(at_idx, 1);
      },
      changed: function(id, fields) {
        var at_idx = find (results, id);
        var oldDoc = results[at_idx];
        var doc = EJSON.clone(oldDoc);
        DiffSequence.applyChanges(doc, fields);
        test.isFalse(at_idx < 0 || at_idx >= results.length);
        test.equal(doc._id, oldDoc._id);
        results[at_idx] = doc;
      },
      movedBefore: function(id, before) {
        var old_idx = find(results, id);
        var new_idx;
        if (before === null)
          new_idx = results.length;
        else
          new_idx = find (results, before);
        if (new_idx > old_idx)
          new_idx--;
        test.isFalse(old_idx < 0 || old_idx >= results.length);
        test.isFalse(new_idx < 0 || new_idx >= results.length);
        results.splice(new_idx, 0, results.splice(old_idx, 1)[0]);
      }
    };

    DiffSequence.diffQueryOrderedChanges(oldResults, newResults, observer);
    test.equal(results, newResults);
  };

  // edge cases and cases run into during debugging
  diffTest(5, [5, 1, 2, 3, 4]);
  diffTest(0, [1, 2, 3, 4]);
  diffTest(4, []);
  diffTest(7, [4, 5, 6, 7, 1, 2, 3]);
  diffTest(7, [5, 6, 7, 1, 2, 3, 4]);
  diffTest(10, [7, 4, 11, 6, 12, 1, 5]);
  diffTest(3, [3, 2, 1]);
  diffTest(10, [2, 7, 4, 6, 11, 3, 8, 9]);
  diffTest(0, []);
  diffTest(1, []);
  diffTest(0, [1]);
  diffTest(1, [1]);
  diffTest(5, [1, 2, 3, 4, 5]);

  // interaction between "changed" and other ops
  diffTest(5, [-5, -1, 2, -3, 4]);
  diffTest(7, [-4, -5, 6, 7, -1, 2, 3]);
  diffTest(7, [5, 6, -7, 1, 2, -3, 4]);
  diffTest(10, [7, -4, 11, 6, 12, -1, 5]);
  diffTest(3, [-3, -2, -1]);
  diffTest(10, [-2, 7, 4, 6, 11, -3, -8, 9]);
});


void OverrideWebkitPrefsHook(content::RenderViewHost* rvh, content::WebPreferences* web_prefs) {

if (package->root()->GetDictionary(switches::kmWebkit, &webkit)) {

#include "extensions/browser/app_window/app_window.h"

#include "nw_content.h"
#include "nw_base.h"

#include "base/command_line.h"
#include "base/files/file_util.h"
#include "base/json/json_reader.h"
#include "base/path_service.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/thread_restrictions.h"

#include "chrome/browser/browser_process.h"
#include "chrome/browser/io_thread.h"
#include "chrome/browser/lifetime/application_lifetime.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/common/chrome_paths.h"

#include "components/crx_file/id_util.h"

#include "content/browser/dom_storage/dom_storage_area.h"
#include "content/common/dom_storage/dom_storage_map.h"
#include "content/nw/src/common/shell_switches.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_types.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/result_codes.h"
#include "content/public/common/user_agent.h"
#include "content/public/renderer/render_view.h"

#include "content/nw/src/nw_content_verifier_delegate.h"
#include "content/nw/src/api/menu/menu.h"
#include "content/nw/src/api/object_manager.h"
#include "content/nw/src/policy_cert_verifier.h"

#include "extensions/browser/event_router.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/renderer/dispatcher.h"
#include "extensions/renderer/script_context.h"
#include "extensions/common/extension.h"
#include "extensions/common/feature_switch.h"
#include "extensions/common/features/feature.h"
#include "extensions/common/manifest.h"
#include "extensions/common/manifest_constants.h"

#include "extensions/grit/extensions_renderer_resources.h"

#include "net/cert/x509_certificate.h"

#include "third_party/WebKit/public/web/WebDocument.h"
#include "third_party/WebKit/public/web/WebFrame.h"
#include "third_party/WebKit/public/web/WebLocalFrame.h"
#include "third_party/WebKit/public/web/WebSecurityPolicy.h"
#include "third_party/WebKit/public/web/WebView.h"
#include "third_party/WebKit/public/web/WebDocument.h"
#include "third_party/WebKit/public/web/WebLocalFrame.h"
#include "third_party/WebKit/public/web/WebScriptSource.h"

#include "chrome/common/chrome_constants.h"

#include "ui/base/resource/resource_bundle.h"
#include "ui/base/ui_base_types.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_skia_rep.h"
#include "ui/gfx/codec/png_codec.h"

// NEED TO STAY SYNC WITH NODE
#ifndef NODE_CONTEXT_EMBEDDER_DATA_INDEX
#define NODE_CONTEXT_EMBEDDER_DATA_INDEX 32
#endif

#include "third_party/node/src/node_webkit.h"
#include "third_party/WebKit/public/web/WebScopedMicrotaskSuppression.h"
#include "nw/id/commit.h"
#include "content/nw/src/nw_version.h"

#undef LOG
#undef ASSERT
#undef FROM_HERE

#if defined(OS_WIN)
#define _USE_MATH_DEFINES
#include <math.h>
#endif
#include "third_party/WebKit/Source/config.h"
#include "third_party/WebKit/Source/core/frame/Frame.h"
#include "third_party/WebKit/Source/web/WebLocalFrameImpl.h"
#include "V8HTMLElement.h"

#include "content/renderer/render_view_impl.h"
#include "base/logging.h"

using content::RenderView;
using content::RenderViewImpl;
using extensions::ScriptContext;
using extensions::Extension;
using extensions::EventRouter;
using extensions::Manifest;
using extensions::Feature;
using extensions::ExtensionPrefs;
using extensions::ExtensionRegistry;
using extensions::Dispatcher;
using extensions::ContentVerifierDelegate;
using extensions::NWContentVerifierDelegate;
using blink::WebScriptSource;

namespace manifest_keys = extensions::manifest_keys;

CallTickCallbackFn g_call_tick_callback_fn = nullptr;
SetupNWNodeFn g_setup_nwnode_fn = nullptr;
IsNodeInitializedFn g_is_node_initialized_fn = nullptr;
SetNWTickCallbackFn g_set_nw_tick_callback_fn = nullptr;
StartNWInstanceFn g_start_nw_instance_fn = nullptr;
GetNodeContextFn g_get_node_context_fn = nullptr;
SetNodeContextFn g_set_node_context_fn = nullptr;
GetNodeEnvFn g_get_node_env_fn = nullptr;
GetCurrentEnvironmentFn g_get_current_env_fn = nullptr;
EmitExitFn g_emit_exit_fn = nullptr;
RunAtExitFn g_run_at_exit_fn = nullptr;

namespace nw {

namespace {

extensions::Dispatcher* g_dispatcher = NULL;
bool g_reloading_app = false;

static inline v8::Local<v8::String> v8_str(const char* x) {
  v8::Isolate* isolate = v8::Isolate::GetCurrent();
  return v8::String::NewFromUtf8(isolate, x);
}

v8::Handle<v8::Value> CallNWTickCallback(void* env, const v8::Handle<v8::Value> ret) {
  blink::WebScopedMicrotaskSuppression suppression;
  g_call_tick_callback_fn(env);
  return Undefined(v8::Isolate::GetCurrent());
}

v8::Handle<v8::Value> CreateNW(ScriptContext* context,
                               v8::Handle<v8::Object> node_global,
                               v8::Handle<v8::Context> node_context) {
  v8::Handle<v8::String> nw_string(
      v8::String::NewFromUtf8(context->isolate(), "nw"));
  v8::Handle<v8::Object> global(context->v8_context()->Global());
  v8::Handle<v8::Value> nw(global->Get(nw_string));
  if (nw->IsUndefined()) {
    nw = v8::Object::New(context->isolate());;
    //node_context->Enter();
    global->Set(nw_string, nw);
    //node_context->Exit();
  }
  return nw;
}

// Returns |value| cast to an object if possible, else an empty handle.
v8::Handle<v8::Object> AsObjectOrEmpty(v8::Handle<v8::Value> value) {
  return value->IsObject() ? value.As<v8::Object>() : v8::Handle<v8::Object>();
}

} //namespace

bool GetPackageImage(nw::Package* package,
                     const FilePath& icon_path,
                     gfx::Image* image) {
  FilePath path = package->ConvertToAbsoutePath(icon_path);

  // Read the file from disk.
  std::string file_contents;
  if (path.empty() || !base::ReadFileToString(path, &file_contents))
    return false;

  // Decode the bitmap using WebKit's image decoder.
  const unsigned char* data =
      reinterpret_cast<const unsigned char*>(file_contents.data());
  scoped_ptr<SkBitmap> decoded(new SkBitmap());
  // Note: This class only decodes bitmaps from extension resources. Chrome
  // doesn't (for security reasons) directly load extension resources provided
  // by the extension author, but instead decodes them in a separate
  // locked-down utility process. Only if the decoding succeeds is the image
  // saved from memory to disk and subsequently used in the Chrome UI.
  // Chrome is therefore decoding bitmaps here that were generated by Chrome.
  gfx::PNGCodec::Decode(data, file_contents.length(), decoded.get());
  if (decoded->empty())
    return false;  // Unable to decode.

  *image = gfx::Image::CreateFrom1xBitmap(*decoded);
  return true;
}

int MainPartsPreCreateThreadsHook() {
  base::ThreadRestrictions::ScopedAllowIO allow_io;
  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
  nw::Package* package = InitNWPackage();
  if (package && !package->path().empty()) {
    base::FilePath path = package->path().NormalizePathSeparators();

    command_line->AppendSwitchPath("nwapp", path);
    int dom_storage_quota_mb;
    if (package->root()->GetInteger("dom_storage_quota", &dom_storage_quota_mb)) {
      content::DOMStorageMap::SetQuotaOverride(dom_storage_quota_mb * 1024 * 1024);
    }

    base::FilePath user_data_dir;
    std::string name;
    package->root()->GetString("name", &name);
    if (!name.empty() && PathService::Get(chrome::DIR_USER_DATA, &user_data_dir)) {
      base::FilePath old_dom_storage = user_data_dir
        .Append(FILE_PATH_LITERAL("Local Storage"))
        .Append(FILE_PATH_LITERAL("file__0.localstorage"));
      if (base::PathExists(old_dom_storage)) {
        std::string id = crx_file::id_util::GenerateId(name);
        GURL origin("chrome-extension://" + id + "/");
        base::FilePath new_storage_dir = user_data_dir.Append(FILE_PATH_LITERAL("Default"))
          .Append(FILE_PATH_LITERAL("Local Storage"));
        base::CreateDirectory(new_storage_dir);

        base::FilePath new_dom_storage = new_storage_dir
          .Append(content::DOMStorageArea::DatabaseFileNameFromOrigin(origin));
        base::FilePath new_dom_journal = new_dom_storage.ReplaceExtension(FILE_PATH_LITERAL("localstorage-journal"));
        base::FilePath old_dom_journal = old_dom_storage.ReplaceExtension(FILE_PATH_LITERAL("localstorage-journal"));
        base::Move(old_dom_journal, new_dom_journal);
        base::Move(old_dom_storage, new_dom_storage);
        LOG_IF(INFO, true) << "Migrate DOM storage from " << old_dom_storage.AsUTF8Unsafe() << " to " << new_dom_storage.AsUTF8Unsafe();
      }
    }

    const base::ListValue *additional_trust_anchors = NULL;
    if (package->root()->GetList("additional_trust_anchors", &additional_trust_anchors)) {
      net::CertificateList trust_anchors;
      for (size_t i=0; i<additional_trust_anchors->GetSize(); i++) {
        std::string certificate_string;
        if (!additional_trust_anchors->GetString(i, &certificate_string)) {
          // LOG(WARNING)
          //   << "Could not get string from entry " << i;
          continue;
        }

        net::CertificateList loaded =
          net::X509Certificate::CreateCertificateListFromBytes(
              certificate_string.c_str(), certificate_string.size(),
              net::X509Certificate::FORMAT_AUTO);
        if (loaded.empty() && !certificate_string.empty()) {
          // LOG(WARNING)
          //   << "Could not load certificate from entry " << i;
          continue;
        }

        trust_anchors.insert(trust_anchors.end(), loaded.begin(), loaded.end());
      }
      if (!trust_anchors.empty()) {
        // LOG(INFO)
        //   << "Added " << trust_anchors.size() << " certificates to trust anchors.";
        PolicyCertVerifier* verifier =
          (PolicyCertVerifier*)g_browser_process->io_thread()->globals()->cert_verifier.get();
        verifier->SetTrustAnchors(trust_anchors);
      }
    }
  }
  return content::RESULT_CODE_NORMAL_EXIT;
}

void MainPartsPostDestroyThreadsHook() {
  ReleaseNWPackage();
}

void DocumentFinishHook(blink::WebFrame* frame,
                         const extensions::Extension* extension,
                         const GURL& effective_document_url) {
  v8::Isolate* isolate = v8::Isolate::GetCurrent();
  v8::HandleScope hscope(isolate);
  std::string path = effective_document_url.path();
  v8::Local<v8::Context> v8_context = frame->mainWorldScriptContext();
  std::string root_path = extension->path().AsUTF8Unsafe();
  base::FilePath root(extension->path());
  RenderViewImpl* rv = RenderViewImpl::FromWebView(frame->view());
  if (!rv)
    return;
  std::string js_fn = rv->renderer_preferences().nw_inject_js_doc_end;
  if (js_fn.empty())
    return;
  base::FilePath js_file = root.AppendASCII(js_fn);
  std::string content;
  if (!base::ReadFileToString(js_file, &content)) {
    //LOG(WARNING) << "Failed to load js script file: " << js_file.value();
    return;
  }
  base::string16 jscript = base::UTF8ToUTF16(content);
  {
    blink::WebScopedMicrotaskSuppression suppression;
    v8::Context::Scope cscope(v8_context);
    // v8::Handle<v8::Value> result;
    frame->executeScriptAndReturnValue(WebScriptSource(jscript));
  }
}

void DocumentHook2(bool start, content::RenderFrame* frame, Dispatcher* dispatcher) {
  // ignore the first invocation of this hook for iframe
  // or we'll trigger creating a context with invalid type
  // there will follow another one with valid url
  blink::WebLocalFrame* web_frame = frame->GetWebFrame();
  GURL frame_url = ScriptContext::GetDataSourceURLForFrame(web_frame);
  if (web_frame->parent() && (!frame_url.is_valid() || frame_url.is_empty()))
    return;
  v8::Isolate* isolate = v8::Isolate::GetCurrent();
  v8::HandleScope scope(isolate);
  v8::Local<v8::Context> v8_context = frame->GetRenderView()
      ->GetWebView()->mainFrame()->mainWorldScriptContext();
  ScriptContext* script_context =
      dispatcher->script_context_set().GetByV8Context(v8_context);
  if (!script_context || !script_context->extension())
    return;
  if (script_context->extension()->GetType() == Manifest::TYPE_NWJS_APP &&
      script_context->context_type() == Feature::BLESSED_EXTENSION_CONTEXT) {
    std::vector<v8::Handle<v8::Value> > arguments;
    v8::Local<v8::Value> window =
      web_frame->mainWorldScriptContext()->Global();
    arguments.push_back(v8::Boolean::New(isolate, start));
    arguments.push_back(window);
    script_context->module_system()->CallModuleMethod("nw.Window", "onDocumentStartEnd", &arguments);
  }
}

void DocumentElementHook(blink::WebLocalFrame* frame,
                         const extensions::Extension* extension,
                         const GURL& effective_document_url) {
  // ignore the first invocation of this hook for iframe
  // or we'll trigger creating a context with invalid type
  // there will follow another one with valid url
  GURL frame_url = ScriptContext::GetDataSourceURLForFrame(frame);
  if (frame->parent() && (!frame_url.is_valid() || frame_url.is_empty()))
    return;
  v8::Isolate* isolate = v8::Isolate::GetCurrent();
  v8::HandleScope hscope(isolate);
  frame->document().securityOrigin().grantUniversalAccess();
  frame->setNodeJS(true);
  std::string path = effective_document_url.path();
  v8::Local<v8::Context> v8_context = frame->mainWorldScriptContext();
  std::string root_path = extension->path().AsUTF8Unsafe();
  base::FilePath root(extension->path());
  if (!v8_context.IsEmpty()) {
    blink::WebScopedMicrotaskSuppression suppression;
    v8::Context::Scope cscope(v8_context);
    // Make node's relative modules work
#if defined(OS_WIN)
    base::ReplaceChars(root_path, "\\", "\\\\", &root_path);
#endif
    base::ReplaceChars(root_path, "'", "\\'", &root_path);

    v8::Local<v8::Script> script2 = v8::Script::Compile(v8::String::NewFromUtf8(isolate, (
        "if (typeof nw != 'undefined' && typeof __filename == 'undefined') {"
        "  var root = '" + root_path + "';"
        "  var path = '" + path      + "';"
        "nw.__filename = root + path;"
        "nw.__dirname = root;"
        "}").c_str()),
    v8::String::NewFromUtf8(isolate, "process_main2"));
    CHECK(*script2);
    script2->Run();
  }
  RenderViewImpl* rv = RenderViewImpl::FromWebView(frame->view());
  if (!rv)
    return;

  ui::ResourceBundle* resource_bundle = &ResourceBundle::GetSharedInstance();
  base::StringPiece resource =
      resource_bundle->GetRawDataResource(IDR_NW_PRE13_SHIM_JS);
  if (resource.empty())
    return;
  base::string16 jscript = base::UTF8ToUTF16(resource.as_string());
  if (!v8_context.IsEmpty()) {
    blink::WebScopedMicrotaskSuppression suppression;
    v8::Context::Scope cscope(v8_context);
    frame->executeScriptAndReturnValue(WebScriptSource(jscript));
  }

  std::string js_fn = rv->renderer_preferences().nw_inject_js_doc_start;
  if (js_fn.empty())
    return;
  base::FilePath js_file = root.AppendASCII(js_fn);
  std::string content;
  if (!base::ReadFileToString(js_file, &content)) {
    //LOG(WARNING) << "Failed to load js script file: " << js_file.value();
    return;
  }
  jscript = base::UTF8ToUTF16(content);
  if (!v8_context.IsEmpty()) {
    blink::WebScopedMicrotaskSuppression suppression;
    v8::Context::Scope cscope(v8_context);
    // v8::Handle<v8::Value> result;
    frame->executeScriptAndReturnValue(WebScriptSource(jscript));
  }
}

void ContextCreationHook(blink::WebLocalFrame* frame, ScriptContext* context) {
  v8::Isolate* isolate = context->isolate();

  bool nodejs_enabled = true;
  context->extension()->manifest()->GetBoolean(manifest_keys::kNWJSEnableNode, &nodejs_enabled);

  if (!nodejs_enabled)
    return;

  if (!g_is_node_initialized_fn())
    g_setup_nwnode_fn(0, nullptr);

  bool mixed_context = false;
  context->extension()->manifest()->GetBoolean(manifest_keys::kNWJSMixedContext, &mixed_context);
  v8::Local<v8::Context> node_context;
  g_get_node_context_fn(&node_context);
  if (node_context.IsEmpty() || mixed_context) {
    {
      int argc = 1;
      char argv0[] = "node";
      char* argv[3];
      argv[0] = argv0;
      argv[1] = argv[2] = nullptr;
      std::string main_fn;

      if (context->extension()->manifest()->GetString(manifest_keys::kNWJSInternalMainFilename, &main_fn)) {
        argc = 2;
        argv[1] = strdup(main_fn.c_str());
      }

      v8::Isolate* isolate = v8::Isolate::GetCurrent();
      v8::HandleScope scope(isolate);
      blink::WebScopedMicrotaskSuppression suppression;

      g_set_nw_tick_callback_fn(&CallNWTickCallback);
      v8::Local<v8::Context> dom_context = context->v8_context();
      if (!mixed_context)
        g_set_node_context_fn(isolate, &dom_context);
      dom_context->SetSecurityToken(v8::String::NewFromUtf8(isolate, "nw-token"));
      dom_context->Enter();

      g_start_nw_instance_fn(argc, argv, dom_context);
      {
        v8::Local<v8::Script> script =
          v8::Script::Compile(v8::String::NewFromUtf8(isolate,
                                                      (std::string("process.versions['nw'] = '" NW_VERSION_STRING "';") +
                                                       "process.versions['node-webkit'] = '" NW_VERSION_STRING "';"
                                                       "process.versions['nw-commit-id'] = '" NW_COMMIT_HASH "';"
                                                       "process.versions['chromium'] = '" + chrome::kChromeVersion + "';").c_str()
         ));
        script->Run();
      }

      if (context->extension()->manifest()->GetString(manifest_keys::kNWJSInternalMainFilename, &main_fn)) {
        v8::Local<v8::Script> script = v8::Script::Compile(v8::String::NewFromUtf8(isolate,
         ("global.__filename = '" + main_fn + "';").c_str()));
        script->Run();
      }
      {
        std::string root_path = context->extension()->path().AsUTF8Unsafe();
#if defined(OS_WIN)
        base::ReplaceChars(root_path, "\\", "\\\\", &root_path);
#endif
        base::ReplaceChars(root_path, "'", "\\'", &root_path);
        v8::Local<v8::Script> script = v8::Script::Compile(v8::String::NewFromUtf8(isolate,
         ("global.__dirname = '" + root_path + "';").c_str()));
        script->Run();
      }
      bool content_verification = false;
      if (context->extension()->manifest()->GetBoolean(manifest_keys::kNWJSContentVerifyFlag,
                                                       &content_verification) && content_verification) {
        v8::Local<v8::Script> script =
          v8::Script::Compile(v8::String::NewFromUtf8(isolate,
                                                      (std::string("global.__nwjs_cv = true;") +
                                                       "global.__nwjs_ext_id = '" + context->extension()->id() + "';").c_str()));

        script->Run();
      }

      dom_context->Exit();
    }
  }

  v8::Local<v8::Context> node_context2;
  g_get_node_context_fn(&node_context2);
  if (!mixed_context) {
    v8::Local<v8::Context> g_context =
      v8::Local<v8::Context>::New(isolate, node_context2);
    v8::Local<v8::Object> node_global = g_context->Global();

    context->v8_context()->SetAlignedPointerInEmbedderData(NODE_CONTEXT_EMBEDDER_DATA_INDEX, g_get_node_env_fn());
    context->v8_context()->SetSecurityToken(g_context->GetSecurityToken());

    v8::Handle<v8::Object> nw = AsObjectOrEmpty(CreateNW(context, node_global, g_context));
#if 1
    v8::Local<v8::Array> symbols = v8::Array::New(isolate, 5);
    symbols->Set(0, v8::String::NewFromUtf8(isolate, "global"));
    symbols->Set(1, v8::String::NewFromUtf8(isolate, "process"));
    symbols->Set(2, v8::String::NewFromUtf8(isolate, "Buffer"));
    symbols->Set(3, v8::String::NewFromUtf8(isolate, "root"));
    symbols->Set(4, v8::String::NewFromUtf8(isolate, "require"));

    g_context->Enter();
    for (unsigned i = 0; i < symbols->Length(); ++i) {
      v8::Local<v8::Value> key = symbols->Get(i);
      v8::Local<v8::Value> val = node_global->Get(key);
      nw->Set(key, val);
    }
    g_context->Exit();
#endif
  }

  std::string set_nw_script;
  if (mixed_context)
    set_nw_script = "var nw = global;";
  {
    blink::WebScopedMicrotaskSuppression suppression;
    v8::Context::Scope cscope(context->v8_context());
    // Make node's relative modules work
    std::string root_path = context->extension()->path().AsUTF8Unsafe();
    GURL frame_url = ScriptContext::GetDataSourceURLForFrame(frame);
    std::string url_path = frame_url.path();
#if defined(OS_WIN)
    base::ReplaceChars(root_path, "\\", "\\\\", &root_path);
#endif
    base::ReplaceChars(root_path, "'", "\\'", &root_path);
    v8::Local<v8::Script> script = v8::Script::Compile(v8::String::NewFromUtf8(isolate, (
        set_nw_script +
        "nw.global.XMLHttpRequest = XMLHttpRequest;" +
        // Make node's relative modules work
        "if (typeof nw.process != 'undefined' && "
        "(!nw.process.mainModule.filename || nw.process.mainModule.filename === 'blank' ||"
        "nw.process.mainModule.filename.indexOf('_generated_background_page.html') >= 0)) {"
        "  var root = '" + root_path + "';"
        "  var p = '" + url_path + "';"
        "nw.process.mainModule.filename = root + p;"
        "nw.process.mainModule.paths = nw.global.require('module')._nodeModulePaths(nw.process.cwd());"
        "nw.process.mainModule.loaded = true;"
        "}").c_str()),
    v8::String::NewFromUtf8(isolate, "process_main"));
    CHECK(*script);
    script->Run();
  }

}

void AmendManifestContentScriptList(base::DictionaryValue* manifest,
                                    const std::string& name,
                                    const std::string& run_at) {
  base::ListValue* scripts = NULL;
  base::Value* temp_value = NULL;
  bool amend = false;

  if (manifest->Get(manifest_keys::kContentScripts, &temp_value))
      if (temp_value->GetAsList(&scripts))
        amend = true;
  if (!scripts)
    scripts = new base::ListValue();

  std::string js;
  if (manifest->GetString(name, &js)) {
    base::ListValue* js_list = new base::ListValue();
    js_list->Append(new base::StringValue(js));

    base::ListValue* matches = new base::ListValue();
    matches->Append(new base::StringValue("<all_urls>"));

    base::DictionaryValue* content_script = new base::DictionaryValue();
    content_script->Set("js", js_list);
    content_script->Set("matches", matches);
    content_script->SetString("run_at", run_at);
    content_script->SetBoolean("in_main_world", true);

    scripts->Append(content_script);

    if (!amend)
      manifest->Set(manifest_keys::kContentScripts, scripts);
  }
}

void AmendManifestStringList(base::DictionaryValue* manifest,
                   const std::string& path,
                   const std::string& string_value) {
  base::ListValue* pattern_list = NULL;
  base::Value* temp_pattern_value = NULL;
  bool amend = false;

  if (manifest->Get(path, &temp_pattern_value))
      if (temp_pattern_value->GetAsList(&pattern_list))
        amend = true;
  if (!pattern_list)
    pattern_list = new base::ListValue();

  pattern_list->Append(new base::StringValue(string_value));
  if (!amend)
    manifest->Set(path, pattern_list);
}

void AmendManifestList(base::DictionaryValue* manifest,
                   const std::string& path,
                   const base::ListValue& list_value) {
  base::ListValue* pattern_list = NULL;

  if (manifest->GetList(path, &pattern_list)) {
    base::ListValue::const_iterator it;
    for(it = list_value.begin(); it != list_value.end(); ++it) {
      pattern_list->Append(*it);
    }
  } else {
    pattern_list = list_value.DeepCopy();
    manifest->Set(path, pattern_list);
  }
}

void LoadNWAppAsExtensionHook(base::DictionaryValue* manifest, std::string* error) {
  if (!manifest)
    return;

  std::string main_url, bg_script, icon_path;
  base::Value *node_remote = NULL;

  nw::Package* package = nw::package();
  manifest->SetBoolean(manifest_keys::kNWJSInternalFlag, true);
  if (error && !package->cached_error_content().empty()) {
    *error = package->cached_error_content();
    return;
  }

  manifest->Set(manifest_keys::kNWJSInternalManifest, package->root()->DeepCopy());

  if (manifest->GetString(manifest_keys::kNWJSMain, &main_url)) {
    if (base::EndsWith(main_url, ".js", base::CompareCase::INSENSITIVE_ASCII)) {
      AmendManifestStringList(manifest, manifest_keys::kPlatformAppBackgroundScripts, main_url);
      manifest->SetString(manifest_keys::kNWJSInternalMainFilename, main_url);
    }else
      AmendManifestStringList(manifest, manifest_keys::kPlatformAppBackgroundScripts, "nwjs/default.js");

    std::string bg_script;
    if (manifest->GetString("bg-script", &bg_script))
      AmendManifestStringList(manifest, manifest_keys::kPlatformAppBackgroundScripts, bg_script);

    AmendManifestStringList(manifest, manifest_keys::kPermissions, "developerPrivate");
    AmendManifestStringList(manifest, manifest_keys::kPermissions, "management");
    AmendManifestStringList(manifest, manifest_keys::kPermissions, "<all_urls>");
  }

  AmendManifestContentScriptList(manifest, "inject_js_start", "document_start");
  AmendManifestContentScriptList(manifest, "inject_js_end",   "document_end");

  if (manifest->GetString("window.icon", &icon_path)) {
    gfx::Image app_icon;
    if (GetPackageImage(package, base::FilePath::FromUTF8Unsafe(icon_path), &app_icon)) {
      int width = app_icon.Width();
      std::string key = "icons." + base::IntToString(width);
      manifest->SetString(key, icon_path);
    }
  }
  if (manifest->Get(switches::kmRemotePages, &node_remote)) {
    //FIXME: node-remote spec different with kWebURLs
    std::string node_remote_string;
    base::ListValue* node_remote_list = NULL;
    if (node_remote->GetAsString(&node_remote_string)) {
      node_remote_list = new base::ListValue();
      node_remote_list->Append(new base::StringValue(node_remote_string));
    } else if (node_remote->GetAsList(&node_remote_list)) {
      // do nothing
    }
    if (node_remote_list)
      AmendManifestList(manifest, manifest_keys::kWebURLs, *node_remote_list);
  }

  if (NWContentVerifierDelegate::GetDefaultMode() == ContentVerifierDelegate::ENFORCE_STRICT)
    manifest->SetBoolean(manifest_keys::kNWJSContentVerifyFlag, true);
}

void RendererProcessTerminatedHook(content::RenderProcessHost* process,
                                   const content::NotificationDetails& details) {
  content::RenderProcessHost::RendererClosedDetails* process_details =
    content::Details<content::RenderProcessHost::RendererClosedDetails>(details).ptr();
  int exit_code = process_details->exit_code;
#if defined(OS_POSIX)
  if (WIFEXITED(exit_code))
    exit_code = WEXITSTATUS(exit_code);
#endif
  SetExitCode(exit_code);
}

void OnRenderProcessShutdownHook(extensions::ScriptContext* context) {
  blink::WebScopedMicrotaskSuppression suppression;
  void* env = g_get_current_env_fn(context->v8_context());
  if (g_is_node_initialized_fn()) {
    g_emit_exit_fn(env);
    g_run_at_exit_fn(env);
  }
}

void KickNextTick() {
  blink::WebScopedMicrotaskSuppression suppression;
  void* env = g_get_node_env_fn();
  if (env)
    g_call_tick_callback_fn(env);
}

void willHandleNavigationPolicy(content::RenderView* rv,
                                blink::WebFrame* frame,
                                const blink::WebURLRequest& request,
                                blink::WebNavigationPolicy* policy,
                                blink::WebString* manifest,
                                bool new_win) {
  v8::Isolate* isolate = v8::Isolate::GetCurrent();
  v8::HandleScope scope(isolate);
  v8::Handle<v8::Context> v8_context =
      rv->GetWebView()->mainFrame()->mainWorldScriptContext();
  ScriptContext* script_context =
      g_dispatcher->script_context_set().GetByV8Context(v8_context);
  //check extension for remote pages, which doesn't have appWindow object
  if (!script_context || !script_context->extension())
    return;
  v8::Context::Scope cscope (v8_context);
  v8::Handle<v8::Value> element = v8::Null(isolate);
  v8::Handle<v8::Object> policy_obj = v8::Object::New(isolate);
#if 0
  blink::LocalFrame* core_frame = blink::toWebLocalFrameImpl(frame)->frame();
  if (core_frame->deprecatedLocalOwner()) {
    element = blink::toV8((blink::HTMLElement*)core_frame->deprecatedLocalOwner(),
                            frame->mainWorldScriptContext()->Global(),
                            frame->mainWorldScriptContext()->GetIsolate());
  }
#endif
  std::vector<v8::Handle<v8::Value> > arguments;
  arguments.push_back(element);
  arguments.push_back(v8_str(request.url().string().utf8().c_str()));
  arguments.push_back(policy_obj);
  if (new_win) {
    script_context->module_system()->CallModuleMethod("nw.Window",
                                                      "onNewWinPolicy", &arguments);
  } else {
    const char* req_context = nullptr;
    switch (request.requestContext()) {
    case blink::WebURLRequest::RequestContextHyperlink:
      req_context = "hyperlink";
      break;
    case blink::WebURLRequest::RequestContextFrame:
      req_context = "form";
      break;
    case blink::WebURLRequest::RequestContextLocation:
      req_context = "location";
      break;
    default:
      break;
    }
    if (req_context) {
      arguments.push_back(v8_str(req_context));
      script_context->module_system()->CallModuleMethod("nw.Window",
                                                        "onNavigation", &arguments);
    }
  }
  v8::Local<v8::Value> manifest_val = policy_obj->Get(v8_str("manifest"));

  //TODO: change this to object
  if (manifest_val->IsString()) {
    v8::String::Utf8Value manifest_str(manifest_val);
    if (manifest)
      *manifest = blink::WebString::fromUTF8(*manifest_str);
  }

  v8::Local<v8::Value> val = policy_obj->Get(v8_str("val"));
  if (!val->IsString())
    return;
  v8::String::Utf8Value policy_str(val);
  if (!strcmp(*policy_str, "ignore"))
    *policy = blink::WebNavigationPolicyIgnore;
  else if (!strcmp(*policy_str, "download"))
    *policy = blink::WebNavigationPolicyDownload;
  else if (!strcmp(*policy_str, "current"))
    *policy = blink::WebNavigationPolicyCurrentTab;
  else if (!strcmp(*policy_str, "new-window"))
    *policy = blink::WebNavigationPolicyNewWindow;
  else if (!strcmp(*policy_str, "new-popup"))
    *policy = blink::WebNavigationPolicyNewPopup;
}

void ExtensionDispatcherCreated(extensions::Dispatcher* dispatcher) {
  g_dispatcher = dispatcher;
}

void CalcNewWinParams(content::WebContents* new_contents, void* params,
                      std::string* nw_inject_js_doc_start,
                      std::string* nw_inject_js_doc_end) {
  extensions::AppWindow::CreateParams ret;
  scoped_ptr<base::Value> val;
  scoped_ptr<base::DictionaryValue> manifest;
  std::string manifest_str = base::UTF16ToUTF8(nw::GetCurrentNewWinManifest());
  val = base::JSONReader().ReadToValue(manifest_str);
  if (val.get() && val->IsType(base::Value::TYPE_DICTIONARY))
    manifest.reset(static_cast<base::DictionaryValue*>(val.release()));
  else
    manifest.reset(new base::DictionaryValue());

  bool resizable;
  if (manifest->GetBoolean(switches::kmResizable, &resizable)) {
    ret.resizable = resizable;
  }
  bool fullscreen;
  if (manifest->GetBoolean(switches::kmFullscreen, &fullscreen) && fullscreen) {
    ret.state = ui::SHOW_STATE_FULLSCREEN;
  }
  int width = 0, height = 0;
  if (manifest->GetInteger(switches::kmWidth, &width))
    ret.content_spec.bounds.set_width(width);
  if (manifest->GetInteger(switches::kmHeight, &height))
    ret.content_spec.bounds.set_height(height);

  int x = 0, y = 0;
  if (manifest->GetInteger(switches::kmX, &x))
    ret.window_spec.bounds.set_x(x);
  if (manifest->GetInteger(switches::kmY, &y))
    ret.window_spec.bounds.set_y(y);
  bool top;
  if (manifest->GetBoolean(switches::kmAlwaysOnTop, &top) && top) {
    ret.always_on_top = true;
  }
  bool frame;
  if (manifest->GetBoolean(switches::kmFrame, &frame) && !frame) {
    ret.frame = extensions::AppWindow::FRAME_NONE;
  }
  bool all_workspaces;
  if (manifest->GetBoolean(switches::kmVisibleOnAllWorkspaces, &all_workspaces)
    && all_workspaces) {
    ret.visible_on_all_workspaces = true;
  }
  gfx::Size& minimum_size = ret.content_spec.minimum_size;
  int min_height = 0, min_width = 0;
  if (manifest->GetInteger(switches::kmMinWidth, &min_width))
    minimum_size.set_width(min_width);
  if (manifest->GetInteger(switches::kmMinHeight, &min_height))
    minimum_size.set_height(min_height);
  int max_height = 0, max_width = 0;
  gfx::Size& maximum_size = ret.content_spec.maximum_size;
  if (manifest->GetInteger(switches::kmMaxWidth, &max_width))
    maximum_size.set_width(max_width);
  if (manifest->GetInteger(switches::kmMaxHeight, &max_height))
    maximum_size.set_height(max_height);

  *(extensions::AppWindow::CreateParams*)params = ret;

  manifest->GetString(switches::kmInjectJSDocStart, nw_inject_js_doc_start);
  manifest->GetString(switches::kmInjectJSDocEnd, nw_inject_js_doc_end);
}

bool GetImage(Package* package, const FilePath& icon_path, gfx::Image* image) {
  FilePath path = package->ConvertToAbsoutePath(icon_path);

  // Read the file from disk.
  std::string file_contents;
  if (path.empty() || !base::ReadFileToString(path, &file_contents))
    return false;

  // Decode the bitmap using WebKit's image decoder.
  const unsigned char* data =
      reinterpret_cast<const unsigned char*>(file_contents.data());
  scoped_ptr<SkBitmap> decoded(new SkBitmap());
  // Note: This class only decodes bitmaps from extension resources. Chrome
  // doesn't (for security reasons) directly load extension resources provided
  // by the extension author, but instead decodes them in a separate
  // locked-down utility process. Only if the decoding succeeds is the image
  // saved from memory to disk and subsequently used in the Chrome UI.
  // Chrome is therefore decoding bitmaps here that were generated by Chrome.
  gfx::PNGCodec::Decode(data, file_contents.length(), decoded.get());
  if (decoded->empty())
    return false;  // Unable to decode.

  *image = gfx::Image::CreateFrom1xBitmap(*decoded);
  return true;
}

bool ExecuteAppCommandHook(int command_id, extensions::AppWindow* app_window) {
#if defined(OS_MACOSX)
  return false;
#else
  //nw::ObjectManager* obj_manager = nw::ObjectManager::Get(app_window->browser_context());
  //Menu* menu = (Menu*)obj_manager->GetApiObject(command_id);
  Menu* menu = app_window->menu_;
  if (!menu)
    return false;
  menu->menu_delegate_->ExecuteCommand(command_id, 0);
  return true;
#endif //OSX
}

void SendEventToApp(const std::string& event_name) {
  Profile* profile = ProfileManager::GetActiveUserProfile();
  const extensions::ExtensionSet& extensions =
    ExtensionRegistry::Get(profile)->enabled_extensions();
  ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile);

  for (extensions::ExtensionSet::const_iterator it = extensions.begin();
       it != extensions.end(); ++it) {
    const Extension* extension = it->get();
    if (extension_prefs->IsExtensionRunning(extension->id()) &&
        extension->location() == extensions::Manifest::COMMAND_LINE) {
      scoped_ptr<base::ListValue> arguments(new base::ListValue());
      scoped_ptr<extensions::Event> event(new extensions::Event(extensions::events::UNKNOWN,
                                                                event_name,
                                                                arguments.Pass()));
      event->restrict_to_browser_context = profile;
      EventRouter::Get(profile)
        ->DispatchEventToExtension(extension->id(), event.Pass());
    }
  }
}

bool ProcessSingletonNotificationCallbackHook(const base::CommandLine& command_line,
                                              const base::FilePath& current_directory) {
  nw::Package* package = nw::package();
  bool single_instance = true;
  package->root()->GetBoolean(switches::kmSingleInstance, &single_instance);
  if (single_instance) {
    SendEventToApp("nw.App.onOpen");
  }

  return single_instance;
}

#if defined(OS_MACOSX)
bool ApplicationShouldHandleReopenHook(bool hasVisibleWindows) {
  SendEventToApp("nw.App.onReopen");
  return true;
}
#endif

static std::string g_user_agent;

void SetUserAgentOverride(const std::string& agent,
                          const std::string& name,
                          const std::string& version) {
  g_user_agent = agent;
  base::ReplaceSubstringsAfterOffset(&g_user_agent, 0, "%name", name);
  base::ReplaceSubstringsAfterOffset(&g_user_agent, 0, "%ver", version);
  base::ReplaceSubstringsAfterOffset(&g_user_agent, 0, "%nwver", NW_VERSION_STRING);
  base::ReplaceSubstringsAfterOffset(&g_user_agent, 0, "%webkit_ver", content::GetWebKitVersion());
  base::ReplaceSubstringsAfterOffset(&g_user_agent, 0, "%osinfo", content::BuildOSInfo());
}

bool GetUserAgentFromManifest(std::string* agent) {
  if (!g_user_agent.empty()) {
    *agent = g_user_agent;
    return true;
  }
  nw::Package* package = nw::package();
  if (!package)
    return false;
  if (package->root()->GetString(switches::kmUserAgent, &g_user_agent)) {
    std::string name, version;
    package->root()->GetString(switches::kmName, &name);
    package->root()->GetString("version", &version);
    SetUserAgentOverride(g_user_agent, name, version);
    *agent = g_user_agent;
    return true;
  }
  return false;
}

void ReloadExtensionHook(const extensions::Extension* extension) {
  g_reloading_app = true;
  chrome::IncrementKeepAliveCount(); //keep alive count is paired with
                                     //appwindow's lifetime, so it's
                                     //needed to keep alive during
                                     //reload
}

void CreateAppWindowHook(extensions::AppWindow* app_window) {
  if (g_reloading_app) {
    g_reloading_app = false;
    chrome::DecrementKeepAliveCount();
  }
}

bool IsReloadingApp() {
  return g_reloading_app;
}

} //namespace nw


// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/nw/src/browser/shell_application_mac.h"

#include "base/auto_reset.h"
#include "base/command_line.h"
#include "url/gurl.h"
#include "content/nw/src/nw_shell.h"
#include "content/nw/src/shell_browser_context.h"
#include "content/nw/src/shell_content_browser_client.h"
#include "content/nw/src/common/shell_switches.h"

@implementation ShellCrApplication

- (BOOL)isHandlingSendEvent {
  return handlingSendEvent_;
}

- (void)sendEvent:(NSEvent*)event {
  base::AutoReset<BOOL> scoper(&handlingSendEvent_, YES);
  [super sendEvent:event];
}

- (void)setHandlingSendEvent:(BOOL)handlingSendEvent {
  handlingSendEvent_ = handlingSendEvent;
}

@end