Repository: jpotts18/mean-stack-relational Branch: master Commit: 14458849606d Files: 75 Total size: 277.6 KB Directory structure: gitextract__zeyph8s/ ├── .gitignore ├── .jshintrc ├── .travis.yml ├── LICENSE ├── Procfile ├── README.md ├── app/ │ ├── controllers/ │ │ ├── articles.js │ │ ├── index.js │ │ └── users.js │ ├── models/ │ │ ├── article.js │ │ └── user.js │ ├── routes/ │ │ ├── articles.js │ │ ├── index.js │ │ └── users.js │ └── views/ │ ├── 404.jade │ ├── 500.jade │ ├── includes/ │ │ ├── foot.jade │ │ └── head.jade │ ├── index.jade │ └── layouts/ │ └── default.jade ├── app.js ├── app.json ├── bower.json ├── config/ │ ├── config.js │ ├── env/ │ │ ├── all.json5 │ │ ├── development.json5.sample │ │ ├── production.json5.sample │ │ ├── test.json5.sample │ │ └── travis.json │ ├── express.js │ ├── middlewares/ │ │ └── session.js │ ├── passport.js │ ├── sequelize.js │ └── winston.js ├── gruntfile.js ├── package.json ├── pm2-ecosystem.json ├── pm2-main.js ├── public/ │ ├── css/ │ │ ├── common.css │ │ └── views/ │ │ └── articles.css │ ├── humans.txt │ ├── img/ │ │ └── .gitignore │ ├── js/ │ │ ├── FbSdk.js │ │ ├── app.js │ │ ├── config.js │ │ ├── controllers/ │ │ │ ├── articles.js │ │ │ ├── header.js │ │ │ ├── index.js │ │ │ └── users/ │ │ │ ├── auth.js │ │ │ ├── signIn.js │ │ │ └── signUp.js │ │ ├── directives.js │ │ ├── filters.js │ │ ├── init.js │ │ └── services/ │ │ ├── articles.js │ │ ├── authenticate.js │ │ └── global.js │ ├── robots.txt │ └── views/ │ ├── 404.html │ ├── articles/ │ │ ├── create.html │ │ ├── edit.html │ │ ├── list.html │ │ └── view.html │ ├── header.html │ ├── index.html │ └── users/ │ ├── auth.html │ ├── signin.html │ └── signup.html └── test/ ├── karma/ │ ├── karma.conf.js │ └── unit/ │ └── controllers/ │ ├── articles.spec.js │ ├── headers.spec.js │ └── index.spec.js └── mocha/ ├── controllers/ │ ├── articleControllerSpec.js │ └── usersControllerSpec.js └── models/ └── userModelSpec.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Github Node Default lib-cov *.seed *.log *.csv *.dat *.out *.pid *.gz pids logs results npm-debug.log node_modules # From Mean Stack bower_components/ .DS_Store .nodemonignore .sass-cache/ node_modules/ public/lib test/coverage/ migrations/ # Configuration Files /config/env/development.json5 /config/env/production.json5 /config/env/test.json5 *.local # IDE files .idea /.*.md.html .settings .project .classpath *~ *.swp *.swo ================================================ FILE: .jshintrc ================================================ { // JSHint Default Configuration File (as on JSHint website) // See http://jshint.com/docs/ for more details "maxerr" : 50, // {int} Maximum error before stopping // Enforcing "bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.) "camelcase" : false, // true: Identifiers must be in camelCase "curly" : true, // true: Require {} for every new block or scope "eqeqeq" : true, // true: Require triple equals (===) for comparison "forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty() "freeze" : true, // true: prohibits overwriting prototypes of native objects such as Array, Date etc. "immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());` "indent" : 4, // {int} Number of spaces to use for indentation "latedef" : false, // true: Require variables/functions to be defined before being used "newcap" : false, // true: Require capitalization of all constructor functions e.g. `new F()` "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee` "noempty" : true, // true: Prohibit use of empty blocks "nonbsp" : true, // true: Prohibit "non-breaking whitespace" characters. "nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment) "plusplus" : false, // true: Prohibit use of `++` and `--` "quotmark" : false, // Quotation mark consistency: // false : do nothing (default) // true : ensure whatever is used is consistent // "single" : require single quotes // "double" : require double quotes "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks) "unused" : false, // Unused variables: // true : all variables, last function parameter // "vars" : all variables only // "strict" : all variables, all function parameters "strict" : true, // true: Requires all functions run in ES5 Strict Mode "maxparams" : false, // {int} Max number of formal params allowed per function "maxdepth" : false, // {int} Max depth of nested blocks (within functions) "maxstatements" : false, // {int} Max number statements per function "maxcomplexity" : false, // {int} Max cyclomatic complexity per function "maxlen" : false, // {int} Max number of characters per line "varstmt" : false, // true: Disallow any var statements. Only `let` and `const` are allowed. // Relaxing "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons) "boss" : false, // true: Tolerate assignments where comparisons would be expected "debug" : false, // true: Allow debugger statements e.g. browser breakpoints. "eqnull" : true, // true: Tolerate use of `== null` "es5" : false, // true: Allow ES5 syntax (ex: getters and setters) "esnext" : true, // true: Allow ES.next (ES6) syntax (ex: `const`) "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features) // (ex: `for each`, multiple try/catch, function expression…) "evil" : false, // true: Tolerate use of `eval` and `new Function()` "expr" : false, // true: Tolerate `ExpressionStatement` as Programs "funcscope" : true, // true: Tolerate defining variables inside control statements "globalstrict" : false, // true: Allow global "use strict" (also enables 'strict') "iterator" : false, // true: Tolerate using the `__iterator__` property "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block "laxbreak" : false, // true: Tolerate possibly unsafe line breakings "laxcomma" : false, // true: Tolerate comma-first style coding "loopfunc" : false, // true: Tolerate functions being defined in loops "multistr" : false, // true: Tolerate multi-line strings "noyield" : false, // true: Tolerate generator functions with no yield statement in them. "notypeof" : false, // true: Tolerate invalid typeof operator values "proto" : false, // true: Tolerate using the `__proto__` property "scripturl" : false, // true: Tolerate script-targeted URLs "shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;` "sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;` "validthis" : false, // true: Tolerate using this in a non-constructor function // Environments "browser" : true, // Web Browser (window, document, etc) "browserify" : false, // Browserify (node.js code in the browser) "couch" : false, // CouchDB "devel" : true, // Development/debugging (alert, confirm, etc) "dojo" : false, // Dojo Toolkit "jasmine" : true, // Jasmine "jquery" : false, // jQuery "mocha" : true, // Mocha "mootools" : false, // MooTools "node" : true, // Node.js "nonstandard" : false, // Widely adopted globals (escape, unescape, etc) "phantom" : false, // PhantomJS "prototypejs" : false, // Prototype and Scriptaculous "qunit" : false, // QUnit "rhino" : false, // Rhino "shelljs" : false, // ShellJS "typed" : false, // Globals for typed array constructions "worker" : false, // Web Workers "wsh" : false, // Windows Scripting Host "yui" : false, // Yahoo User Interface // Custom Globals "globals" : { "angular": true, "describe": true, "it": true, "expect": true, "beforeEach": true, "afterEach": true, "module": true, "inject": true } // additional predefined global variables } ================================================ FILE: .travis.yml ================================================ language: node_js node_js: - "0.10" env: - NODE_ENV=travis services: - mongodb ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2013-2015 Jeff Potter, Chaudhry Junaid Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Procfile ================================================ web: node pm2-main.js ================================================ FILE: README.md ================================================ MEAN Stack Relational ![Mean Stack Build Status](https://travis-ci.org/jpotts18/mean-stack-relational.png) ===================== ### Please use for reference only! No support or updates planned. The main idea for this repository is shamelessly stolen from [http://mean.io](http://mean.io). It says: > MEAN is a boilerplate that provides a nice starting point for [MySQL], Express, Node.js, and AngularJS based applications. It is designed to give you quick and organized way to start developing of MEAN based web apps with useful modules like sequelize and passport pre-bundled and configured. We mainly try to take care of the connection points between existing popular frameworks and solve common integration problems. The MongoDB ORM, [Mongoose](http://mongoosejs.com/), has been replaced with [Sequelize](http://sequelizejs.com/). Switching from mongoose to sequelize allows developers easy access to MySQL, MariaDB, SQLite or PostgreSQL databases by mapping database entries to objects and vice versa. [Addy Osmani's Blog](http://addyosmani.com/blog/full-stack-javascript-with-mean-and-yeoman/) explains SQL databases, being strongly typed in nature are great at enforcing a level of consistency, ensuring many kinds of bad data simply don’t get recorded. By using SQL databases MEAN Stack Relational favors reliability over the performance gains of NoSQL databases. # Demo Deploy to your Heroku account for a demo: [![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy) Note: Deploy from main repository view to avoid missing app.json error. # Getting Started Alright now the fun begins. First clone or download the repo to your computer. 1. Clone the repository ```git clone git@github.com:jpotts18/mean-stack-relational.git```. 1. Go into the repository ```cd mean-stack-relational/```. 1. Install dependencies with NPM ```npm install```. This will copy development.json5, and production.json5 from respective sample files in the config/env folder and run the grunt copy task to copy frontend lib files to their destination. 1. Plug in your private and public keys for working with FB and Twitter into ```/config/env/development.json5``` and/or ```/config/env/production.json5```. 1. Wire up the database connection found in ```/config/env/development.json5``` and/or ```/config/env/production.json5```. 1. Run in production mode with: ```pm2 start pm2-ecosystem.json --env production``` (Run ```sudo npm install -g pm2``` if it's not installed.), or 1. Run in development mode with grunt: ```grunt``` 1. Make something awesome! Thats all! Now go and open up your browser at [http://localhost:3000](http://localhost:3000), and tweet [@jpotts18](http://twitter.com/jpotts18) to say thanks! ## Prerequisites - Node.js - Download and Install Node.js. You can also follow [this gist](https://gist.github.com/isaacs/579814) for a quick and easy way to install Node.js and npm - MySQL - Download and Install MySQL - Make sure it's running on the default port (3306). ### Tool Prerequisites - NPM - Node.js package manager, should be installed when you install node.js. NPM (Node Package Manager) will look at the [package.json](https://github.com/jpotts18/mean-stack-relational/blob/master/package.json) file in the root of the project and download all of the necessary dependencies and put them in a folder called ```node_modules``` - Bower - Web package manager, installing Bower is simple when you have npm: ``` npm install -g bower ``` ### NPM Modules Used - [Passport](http://passportjs.org/) - Passport is authentication middleware for Node.js. Extremely flexible and modular, Passport can be unobtrusively dropped in to any Express-based web application. A comprehensive set of strategies support authentication using a username and password, Facebook, Twitter, and more. - [Express](http://expressjs.com/) - Express is a minimal and flexible node.js web application framework, providing a robust set of features for building single and multi-page, and hybrid web applications. - [Sequelize](http://sequelizejs.com/) - The Sequelize library provides easy access to MySQL, MariaDB, SQLite or PostgreSQL databases by mapping database entries to objects and vice versa. To put it in a nutshell, it's an ORM (Object-Relational-Mapper). The library is written entirely in JavaScript and can be used in the Node.JS environment. ### Javascript Tools Used - [Grunt](http://gruntjs.com/) - In one word: automation. The less work you have to do when performing repetitive tasks like minification, compilation, unit testing, linting, etc, the easier your job becomes. After you've configured it, a Grunt can do most of that mundane work for you—and your team—with basically zero effort. 1. It [watches](https://github.com/jpotts18/mean-stack-relational/blob/master/gruntfile.js#L5) your filesystem and when it detects a change it will livereload your changes. 2. It runs [jshint](https://github.com/jpotts18/mean-stack-relational/blob/master/gruntfile.js#L32) which looks through your javascript files and ensures coding standards. 3. It runs [nodemon](https://github.com/jpotts18/mean-stack-relational/blob/master/gruntfile.js#L35) which watches changes in specific folders and recompiles the app when necessary. No running ```node app.js``` every 2 minutes. 4. It can also run tests like mocha and karma for you. - [Bower](http://bower.io/) - Bower is a package manager for the web. It offers a generic, unopinionated solution to the problem of front-end package management, while exposing the package dependency model via an API that can be consumed by a more opinionated build stack. There are no system wide dependencies, no dependencies are shared between different apps, and the dependency tree is flat. ### Front-End Tools Used - [Angular.js](http://angularjs.org) - AngularJS is an open-source JavaScript framework, maintained by Google, that assists with running single-page applications. Its goal is to augment browser-based applications with model–view–controller (MVC) capability, in an effort to make both development and testing easier. - [Twitter Bootstrap](http://getbootstrap.com/) - Sleek, intuitive, and powerful mobile first front-end framework for faster and easier web development. - [UI Bootstrap](http://angular-ui.github.io/bootstrap/) - Bootstrap components written in pure AngularJS by the AngularUI Team # Project Roadmap Following is a list of items detailing future direction for MEAN Stack Relational: ## Purpose - Demonstrate several login strategies using passport.js - Demonstrate environment configuration best practices - Demonstrate how to use Sequelize to query a single table and to accomplish a join. ## Additions - Demonstrate testing for Express routes and javascript classes using Mocha, Sinon, Proxyquire and more - Demonstrating modularity by using javascript classes for complex backend functionality - Yeoman generator to compete with MEAN # Troubleshooting and Contact During install some of you may encounter some issues feel free to contact me (jpotts18) or the co-contributor (chaudhryjunaid), via the repository issue tracker or the links provided below. I am also available on twitter at [@jpotts18](http://twitter.com/jpotts18) and Junaid at [@chjunaidanwar](http://twitter.com/chjunaidanwar). ================================================ FILE: app/controllers/articles.js ================================================ 'use strict'; /** * Module dependencies. */ var StandardError = require('standard-error'); var db = require('../../config/sequelize'); /** * Find article by id * Note: This is called every time that the parameter :articleId is used in a URL. * Its purpose is to preload the article on the req object then call the next function. */ exports.article = function(req, res, next, id) { console.log('id => ' + id); db.Article.find({where: {id: id}, include: [{model:db.User, attributes:['id', 'username', 'name']}]}).then(function(article){ if(!article) { return next(new Error('Failed to load article ' + id)); } else { req.article = article; return next(); } }).catch(function(err){ return next(err); }); }; /** * Create a article */ exports.create = function(req, res) { // augment the article by adding the UserId req.body.UserId = req.user.id; // save and return and instance of article on the res object. db.Article.create(req.body).then(function(article){ if(!article){ return res.send('users/signup', {errors: new StandardError('Article could not be created')}); } else { return res.jsonp(article); } }).catch(function(err){ return res.send('users/signup', { errors: err, status: 500 }); }); }; /** * Update a article */ exports.update = function(req, res) { // create a new variable to hold the article that was placed on the req object. var article = req.article; article.updateAttributes({ title: req.body.title, content: req.body.content }).then(function(a){ return res.jsonp(a); }).catch(function(err){ return res.render('error', { error: err, status: 500 }); }); }; /** * Delete an article */ exports.destroy = function(req, res) { // create a new variable to hold the article that was placed on the req object. var article = req.article; article.destroy().then(function(){ return res.jsonp(article); }).catch(function(err){ return res.render('error', { error: err, status: 500 }); }); }; /** * Show an article */ exports.show = function(req, res) { // Sending down the article that was just preloaded by the articles.article function // and saves article on the req object. return res.jsonp(req.article); }; /** * List of Articles */ exports.all = function(req, res) { db.Article.findAll({include: [{model:db.User, attributes: ['id', 'username', 'name']}]}).then(function(articles){ return res.jsonp(articles); }).catch(function(err){ return res.render('error', { error: err, status: 500 }); }); }; /** * Article authorizations routing middleware */ exports.hasAuthorization = function(req, res, next) { if (req.article.User.id !== req.user.id) { return res.send(401, 'User is not authorized'); } next(); }; ================================================ FILE: app/controllers/index.js ================================================ 'use strict'; /** * Module dependencies. */ var _ = require('lodash'); exports.render = function(req, res) { res.render('index', { user: req.user ? JSON.stringify(req.user) : "null" }); }; ================================================ FILE: app/controllers/users.js ================================================ 'use strict'; /** * Module dependencies. */ var db = require('../../config/sequelize'), request = require('request'), qs = require('querystring'), config = require('../../config/config'), passport = require('passport'); /** * Auth callback */ exports.authCallback = function (req, res, next) { res.redirect('/'); }; /** * Show login form */ exports.signin = function (req, res) { res.render('users/signin', { title: 'Signin', message: req.flash('error') }); }; /** * Show sign up form */ exports.signup = function (req, res) { res.render('users/signup', { title: 'Sign up', }); }; /** * Logout */ exports.signout = function (req, res) { console.log('Logout: { id: ' + req.user.id + ', username: ' + req.user.username + '}'); req.logout(); return res.send({status: 'success', message: 'User logout successfully.'}); }; /** * Session */ exports.session = function (req, res) { return res.send({status: 'success', message: 'User login successfully.'}) // res.redirect('/'); }; /** * Create user */ exports.create = function (req, res, next) { var message = null; var user = db.User.build(req.body); user.provider = 'local'; user.salt = user.makeSalt(); user.hashedPassword = user.encryptPassword(req.body.password, user.salt); console.log('New User (local) : { id: ' + user.id + ' username: ' + user.username + ' }'); user.save().then(function () { req.login(user, function (err) { if (err) { return next(err); } return res.send({status: 'success', message: 'User signup successfully.'}) // res.redirect('/'); }); }).catch(function (err) { res.render('users/signup', { message: message, user: user }); }); }; /** * Send User */ exports.me = function (req, res) { res.jsonp(req.user || null); }; /** * Find user by id */ exports.user = function (req, res, next, id) { db.User.find({where: {id: id}}).then(function (user) { if (!user) { return next(new Error('Failed to load User ' + id)); } req.profile = user; next(); }).catch(function (err) { next(err); }); }; /** * Generic require login routing middleware */ exports.requiresLogin = function (req, res, next) { if (!req.isAuthenticated()) { return res.status(401).send('User is not authorized'); } next(); }; /** * User authorizations routing middleware */ exports.hasAuthorization = function (req, res, next) { if (req.profile.id !== req.user.id) { return res.status(401).send('User is not authorized'); } next(); }; // function to authenticate and create user function authenticateSocialUser(profile, done) { var searchQuery = { email: profile.email }; if (profile.name) { searchQuery = { twitterKey: profile.id } } db.User.find({where: searchQuery}).then(function (user) { if (!user) { var userObj = {}; if (profile.name) { userObj = { name: profile.name || '', username: profile.name || '', provider: 'twitter', twitterKey: profile.id, email: profile.id + "@twitter.com" } } else { userObj = { name: profile.given_name || '', email: profile.email, username: profile.given_name || profile.name || '', provider: 'google', googleUserId: profile.sub }; } db.User.create(userObj).then(function (u) { done(null, u); }) } else { done(null, user); } }).catch(function (err) { done(err, null, err); }); } exports.facebookUser = function (req, res, next) { function sendResponse(err, user, info) { if (err) { return res.send({status: "error", error: err}); } if (!user) { return res.send({status: "error", error: info}); } req.login(user, function (err) { if (err) { return res.send({status: "error", error: {message: 'Login failed'}}); } return res.send({status: "success", data: {user: user}}); }); } passport.authenticate('facebook-token', {scope: ['email', 'user_about_me', 'phone']}, sendResponse)(req, res, next); } exports.twitterSocialUser = function (req, res) { var requestTokenUrl = 'https://api.twitter.com/oauth/request_token'; var accessTokenUrl = 'https://api.twitter.com/oauth/access_token'; var profileUrl = 'https://api.twitter.com/1.1/account/verify_credentials.json'; function sendResponse(err, user, info) { if (err) { return res.send({status: "error", error: err}); } if (!user) { return res.send({status: "error", error: info}); } req.login(user, function (err) { if (err) { return res.send({status: "error", error: {message: 'Login failed'}}); } return res.send({status: "success", data: {user: user}}); }); } function getUserProfile(err, response, profile) { if (err) { return res.send({status: "error", error: err}); } // Step 5a. Link user accounts. authenticateSocialUser(profile, sendResponse); } function verifyAccesToken(err, response, accessToken) { accessToken = qs.parse(accessToken); var profileOauth = { consumer_key: config.twitter.clientID, consumer_secret: config.twitter.clientSecret, token: accessToken.oauth_token, token_secret: accessToken.oauth_token_secret, }; // Step 4. Retrieve user's profile information and email address. request.get({ url: profileUrl, qs: {include_email: true}, oauth: profileOauth, json: true }, getUserProfile); } function obtainRequestToken() { var requestTokenOauth = { consumer_key: config.twitter.clientID, consumer_secret: config.twitter.clientSecret, callback: req.body.redirectUri }; // Step 1. Obtain request token for the authorization popup. request.post({url: requestTokenUrl, oauth: requestTokenOauth}, function (err, response, body) { var oauthToken = qs.parse(body); // Step 2. Send OAuth token back to open the authorization screen. res.send(oauthToken); }); } function exchangeOauthToken() { var accessTokenOauth = { consumer_key: config.twitter.clientID, consumer_secret: config.twitter.clientSecret, token: req.body.oauth_token, verifier: req.body.oauth_verifier }; // Step 3. Exchange oauth token and oauth verifier for access token. request.post({url: accessTokenUrl, oauth: accessTokenOauth}, verifyAccesToken); } // Part 1 of 2: Initial request from Satellizer. if (!req.body.oauth_token || !req.body.oauth_verifier) { obtainRequestToken(); } else { // Part 2 of 2: Second request after Authorize app is clicked. exchangeOauthToken(); } } exports.googleSocailUser = function (req, res) { var accessTokenUrl = 'https://accounts.google.com/o/oauth2/token'; var peopleApiUrl = 'https://www.googleapis.com/plus/v1/people/me/openIdConnect'; var params = { code: req.body.code, client_id: req.body.clientId, client_secret: config.google.clientSecret, redirect_uri: req.body.redirectUri, grant_type: 'authorization_code' }; function sendResponse(err, user, info) { if (err) { return res.send({status: "error", error: err}); } if (!user) { return res.send({status: "error", error: info}); } req.login(user, function (err) { if (err) { return res.send({status: "error", error: {message: 'Login failed'}}); } return res.send({status: "success", data: {user: user}}); }); } function retrivedInfo(err, response, profile) { if (profile.error) { return res.status(500).send({message: profile.error.message}); } // Authenticate User authenticateSocialUser(profile, sendResponse); } function getAccessToken(err, response, token) { var accessToken = token.access_token; var headers = {Authorization: 'Bearer ' + accessToken}; // Step 2. Retrieve profile information about the current user. request.get({url: peopleApiUrl, headers: headers, json: true}, retrivedInfo); } // Step 1. Exchange authorization code for access token. request.post(accessTokenUrl, {json: true, form: params}, getAccessToken); } ================================================ FILE: app/models/article.js ================================================ 'use strict'; module.exports = function(sequelize, DataTypes) { var Article = sequelize.define('Article', { title: DataTypes.STRING, content: DataTypes.TEXT }, { associate: function(models){ Article.belongsTo(models.User); } } ); return Article; }; ================================================ FILE: app/models/user.js ================================================ 'use strict'; /** * User Model */ var crypto = require('crypto'); module.exports = function(sequelize, DataTypes) { var User = sequelize.define('User', { name: DataTypes.STRING, email: DataTypes.STRING, username: DataTypes.STRING, hashedPassword: DataTypes.STRING, provider: DataTypes.STRING, salt: DataTypes.STRING, facebookUserId: DataTypes.INTEGER, twitterUserId: DataTypes.INTEGER, twitterKey: DataTypes.STRING, twitterSecret: DataTypes.STRING, github: DataTypes.STRING, openId: DataTypes.STRING }, { instanceMethods: { toJSON: function () { var values = this.get(); delete values.hashedPassword; delete values.salt; return values; }, makeSalt: function() { return crypto.randomBytes(16).toString('base64'); }, authenticate: function(plainText){ return this.encryptPassword(plainText, this.salt) === this.hashedPassword; }, encryptPassword: function(password, salt) { if (!password || !salt) { return ''; } salt = new Buffer(salt, 'base64'); return crypto.pbkdf2Sync(password, salt, 10000, 64).toString('base64'); } }, associate: function(models) { User.hasMany(models.Article); } } ); return User; }; ================================================ FILE: app/routes/articles.js ================================================ 'use strict'; /** * Module dependencies. */ var users = require('../../app/controllers/users'), articles = require('../../app/controllers/articles'); module.exports = function(app) { // Article Routes app.route('/articles') .get(articles.all) .post(users.requiresLogin, articles.create); app.route('/articles/:articleId') .get(articles.show) .put(users.requiresLogin, articles.hasAuthorization, articles.update) .delete(users.requiresLogin, articles.hasAuthorization, articles.destroy); // Finish with setting up the articleId param // Note: the articles.article function will be called everytime then it will call the next function. app.param('articleId', articles.article); }; ================================================ FILE: app/routes/index.js ================================================ 'use strict'; module.exports = function(app) { // Home route var index = require('../../app/controllers/index'); app.get('/', index.render); }; ================================================ FILE: app/routes/users.js ================================================ 'use strict'; /** * Module dependencies. */ var passport = require('passport'); module.exports = function (app) { // User Routes var users = require('../../app/controllers/users'); // User Routes app.get('/signout', users.signout); app.get('/users/me', users.me); // Setting up the users api app.post('/users', users.create); // Setting the local strategy route app.post('/users/session', passport.authenticate('local', { failureRedirect: '/signin', failureFlash: true }), users.session); // Setting social authentication routes // Setting the facebook oauth route app.post('/auth/facebook/token', users.facebookUser); app.post('/auth/google', users.googleSocailUser); // Setting the twitter oauth route app.post('/auth/twitter', users.twitterSocialUser); // Finish with setting up the userId param app.param('userId', users.user); }; ================================================ FILE: app/views/404.jade ================================================ extends layouts/default block main h1 Oops something went wrong br span 404 block content #error-message-box #error-stack-trace pre code!= error ================================================ FILE: app/views/500.jade ================================================ extends layouts/default block main h1 Oops something went wrong br span 500 block content #error-message-box #error-stack-trace pre code!= error ================================================ FILE: app/views/includes/foot.jade ================================================ //AngularJS script(type='text/javascript', src='/lib/angular/angular.js') script(type='text/javascript', src='/lib/angular-cookies/angular-cookies.js') script(type='text/javascript', src='/lib/angular-resource/angular-resource.js') script(type='text/javascript', src='/lib/angular-ui-router/release/angular-ui-router.min.js') //Angular UI script(type='text/javascript', src='/lib/angular-bootstrap/ui-bootstrap.js') script(type='text/javascript', src='/lib/angular-bootstrap/ui-bootstrap-tpls.js') script(type='text/javascript', src='/lib/angular-ui-utils/modules/route/route.js') script(type='text/javascript', src='/lib/satellizer/satellizer.min.js') script(type='text/javascript', src='/js/FbSdk.js') script(type='text/javascript', src='/lib/social/angular-fblogin.js') //Application Init script(type='text/javascript', src='/js/app.js') script(type='text/javascript', src='/js/config.js') script(type='text/javascript', src='/js/directives.js') script(type='text/javascript', src='/js/filters.js') //Application Services script(type='text/javascript', src='/js/services/global.js') script(type='text/javascript', src='/js/services/articles.js') script(type='text/javascript', src='/js/services/authenticate.js') //Application Controllers script(type='text/javascript', src='/js/controllers/articles.js') script(type='text/javascript', src='/js/controllers/index.js') script(type='text/javascript', src='/js/controllers/header.js') script(type='text/javascript', src='/js/controllers/users/auth.js') script(type='text/javascript', src='/js/controllers/users/signIn.js') script(type='text/javascript', src='/js/controllers/users/signUp.js') script(type='text/javascript', src='/js/init.js') if (process.env.NODE_ENV == 'development') //Livereload script rendered script(type='text/javascript', src='http://localhost:35729/livereload.js') ================================================ FILE: app/views/includes/head.jade ================================================ head meta(charset='utf-8') meta(http-equiv='X-UA-Compatible', content='IE=edge,chrome=1') meta(name='viewport', content='width=device-width,initial-scale=1') title= appName+' - '+title meta(http-equiv='Content-type', content='text/html;charset=UTF-8') meta(name="keywords", content="node.js, express, mongoose, mongodb, angularjs") meta(name="description", content="MEAN - A Modern Stack: MongoDB, ExpressJS, AngularJS, NodeJS. (BONUS: Passport User Support).") link(href='/img/icons/favicon.ico', rel='shortcut icon', type='image/x-icon') meta(property='fb:app_id', content='APP_ID') meta(property='og:title', content='#{appName} - #{title}') meta(property='og:description', content='MEAN - A Modern Stack: MongoDB, ExpressJS, AngularJS, NodeJS. (BONUS: Passport User Support).') meta(property='og:type', content='website') meta(property='og:url', content='APP_URL') meta(property='og:image', content='APP_LOGO') meta(property='og:site_name', content='MEAN - A Modern Stack') meta(property='fb:admins', content='APP_ADMIN') link(rel='stylesheet', href='/lib/bootstrap/docs/assets/css/bootstrap.css') //- link(rel='stylesheet', href='/lib/bootstrap/dist/css/bootstrap-responsive.css') link(rel='stylesheet', href='/css/common.css') link(rel='stylesheet', href='/css/views/articles.css') //if lt IE 9 script(src='http://html5shim.googlecode.com/svn/trunk/html5.js') ================================================ FILE: app/views/index.jade ================================================ extends layouts/default block content section(ui-view) script(type="text/javascript"). window.user = !{user}; ================================================ FILE: app/views/layouts/default.jade ================================================ doctype html html(lang='en', xmlns='http://www.w3.org/1999/xhtml', xmlns:fb='https://www.facebook.com/2008/fbml', itemscope='itemscope', itemtype='http://schema.org/Product') base(href='/') include ../includes/head body .navbar.navbar-inverse.navbar-fixed-top(data-ng-include="'views/header.html'", data-role="navigation") section.content section.container block content include ../includes/foot ================================================ FILE: app.js ================================================ 'use strict'; /** * Module dependencies. */ var express = require('express'); var fs = require('fs'); /** * Main application entry file. * Please note that the order of loading is important. */ // Load Configurations var config = require('./config/config'); var winston = require('./config/winston'); winston.info('Starting '+config.app.name+'...'); winston.info('Config loaded: '+config.NODE_ENV); winston.debug('Accepted Config:',config); var db = require('./config/sequelize'); var passport = require('./config/passport'); var app = express(); //Initialize Express require('./config/express')(app, passport); //Start the app by listening on app.listen(config.PORT); winston.info('Express app started on port ' + config.PORT); //expose app module.exports = app; ================================================ FILE: app.json ================================================ { "name": "mean-stack-relational", "description": "M*EAN - A Modern Stack: MySQL(Postgres for Heroku), ExpressJS, AngularJS, NodeJS. (BONUS: Passport User Support).", "repository": "https://github.com/jpotts18/mean-stack-relational.git", "logo": "https://raw.githubusercontent.com/jpotts18/mean-stack-relational/master/public/img/m_logo.png", "keywords": ["node", "express", "static"], "env": { "WEB_CONCURRENCY": { "description": "The number of processes to run.", "value": "1" } }, "addons": [ "heroku-postgresql:hobby-dev" ] } ================================================ FILE: bower.json ================================================ { "name": "mean", "version": "0.1.0", "dependencies": { "angular": "latest", "angular-resource": "latest", "angular-cookies": "latest", "angular-mocks": "latest", "angular-route": "latest", "bootstrap": "2.3.2", "angular-bootstrap": "0.7.0", "angular-ui-utils": "0.0.4", "angular-ui-router": "~0.3.1", "satellizer": "^0.15.5", "bower": "*", "install": "^1.0.4", "angular-fblogin": "*" } } ================================================ FILE: config/config.js ================================================ 'use strict'; var nconf = require('nconf'), json5 = require('json5'), _ = require('lodash'), glob = require('glob'), path = require('path'), fs = require('fs'), StandardError = require('standard-error'); var rootPath = path.normalize(__dirname + '/..'); // Load app configuration var computedConfig = { root: rootPath, modelsDir : rootPath + '/app/models' }; // // Setup nconf to use (in-order): // 1. Locally computed config // 2. Command-line arguments // 3. Some Environment variables // 4. Some defaults // 5. Environment specific config file located at './env/.json' // 6. Shared config file located at './env/all.json' // nconf.argv() .env(['PORT','NODE_ENV','FORCE_DB_SYNC','forceSequelizeSync'])// Load select environment variables .defaults({store:{ NODE_ENV:'development' }}); var envConfigPath = rootPath + '/config/env/'+nconf.get('NODE_ENV')+'.json5'; try{ if(!fs.statSync(envConfigPath).isFile()){ throw new Error(); // throw error to trigger catch } } catch(err){ throw new StandardError('Environment specific config file not found! Throwing up! (NODE_ENV=' +nconf.get('NODE_ENV')+')'); } nconf.file(nconf.get('NODE_ENV'),{ file: envConfigPath, type:'file', format:json5 }) .file('shared',{ file: rootPath+ '/config/env/all.json5', type:'file', format:json5 }) .add('base-defaults',{type:'literal', store:{ PORT:5555 }}) .overrides({store:computedConfig}); module.exports = nconf.get(); /** * Get files by glob patterns */ module.exports.getGlobbedFiles = function(globPatterns, removeRoot) { // For context switching var _this = this; // URL paths regex var urlRegex = new RegExp('^(?:[a-z]+:)?\/\/', 'i'); // The output array var output = []; // If glob pattern is array so we use each pattern in a recursive way, otherwise we use glob if (_.isArray(globPatterns)) { globPatterns.forEach(function(globPattern) { output = _.union(output, _this.getGlobbedFiles(globPattern, removeRoot)); }); } else if (_.isString(globPatterns)) { if (urlRegex.test(globPatterns)) { output.push(globPatterns); } else { var files = glob(globPatterns, { sync: true }); if (removeRoot) { files = files.map(function(file) { return file.replace(removeRoot, ''); }); } output = _.union(output, files); } } return output; }; /** * Get the modules JavaScript files */ module.exports.getJavaScriptAssets = function(includeTests) { var output = this.getGlobbedFiles(this.assets.lib.js.concat(this.assets.js), 'public/'); // To include tests if (includeTests) { output = _.union(output, this.getGlobbedFiles(this.assets.tests)); } return output; }; /** * Get the modules CSS files */ module.exports.getCSSAssets = function() { var output = this.getGlobbedFiles(this.assets.lib.css.concat(this.assets.css), 'public/'); return output; }; ================================================ FILE: config/env/all.json5 ================================================ { PORT: 3000, // capital PORT allows auto override by common env variable if it exists FORCE_DB_SYNC: "false", // must be string to allow environment variable override enableSequelizeLog: "true", // type chosen as string for no particular reason expressSessionSecret: '$uper$ecret$e$$ionKey' // replace with your own in production } ================================================ FILE: config/env/development.json5.sample ================================================ { // This is your MYSQL Database configuration db: { name: "mean_relational", password: "", username: "root", host: "localhost", port: 3306 }, app: { name: "M*EAN Stack Relational - Development" }, // You will need to get your own client id's before this will work properly facebook: { clientID: "", clientSecret: "", callbackURL: "http://localhost:3000/auth/facebook/callback" }, twitter: { clientID: "", clientSecret: "", callbackURL: "http://localhost:3000/auth/twitter/callback" }, google: { realm: "http://localhost:3000/", callbackURL: "http://localhost:3000/auth/google/callback" } } ================================================ FILE: config/env/production.json5.sample ================================================ { // This is your MYSQL Database configuration db: { name: "mean_relational", password: "", username: "root", host:"localhost", port:3306 }, app: { name: "M*EAN Stack Relational - Production" }, // You will need to get your own client id's before this will work properly facebook: { clientID: "", clientSecret: "", callbackURL: "http://localhost:3000/auth/facebook/callback" }, twitter: { clientID: "", clientSecret: "", callbackURL: "http://localhost:3000/auth/twitter/callback" }, google: { realm: "http://localhost:3000/", callbackURL: "http://localhost:3000/auth/google/callback" } } ================================================ FILE: config/env/test.json5.sample ================================================ { PORT: 3001, // This is your MYSQL test database configuration db: { name: "mean_relational_test", password: "", username: "root", host: "localhost", port: 3306 }, app: { name: "M*EAN Stack Relational - Test" }, // You will need to get your own client id's before this will work properly facebook: { clientID: "", clientSecret: "", callbackURL: "http://localhost:3000/auth/facebook/callback" }, twitter: { clientID: "", clientSecret: "", callbackURL: "http://localhost:3000/auth/twitter/callback" }, google: { realm: "http://localhost:3000/", callbackURL: "http://localhost:3000/auth/google/callback" } } ================================================ FILE: config/env/travis.json ================================================ { "db": "mongodb://localhost/mean-travis", "port": 3001, "app": { "name": "MEAN - A Modern Stack - Test on travis" }, "facebook": { "clientID": "APP_ID", "clientSecret": "APP_SECRET", "callbackURL": "http://localhost:3000/auth/facebook/callback" }, "twitter": { "clientID": "CONSUMER_KEY", "clientSecret": "CONSUMER_SECRET", "callbackURL": "http://localhost:3000/auth/twitter/callback" }, "github": { "clientID": "APP_ID", "clientSecret": "APP_SECRET", "callbackURL": "http://localhost:3000/auth/github/callback" }, "google": { "clientID": "APP_ID", "clientSecret": "APP_SECRET", "callbackURL": "http://localhost:3000/auth/google/callback" } } ================================================ FILE: config/express.js ================================================ 'use strict'; /** * Module dependencies. */ var express = require('express'); var flash = require('connect-flash'); var helpers = require('view-helpers'); var compression = require('compression'); var favicon = require('serve-favicon'); var logger = require('morgan'); var cookieParser = require('cookie-parser'); var bodyParser = require('body-parser'); var methodOverride = require('method-override'); var path = require('path'); var sessionMiddleware = require('./middlewares/session'); var config = require('./config'); var winston = require('./winston'); module.exports = function(app, passport) { winston.info('Initializing Express'); app.set('showStackError', true); //Prettify HTML app.locals.pretty = true; //Should be placed before express.static app.use(compression({ filter: function(req, res) { return (/json|text|javascript|css/).test(res.getHeader('Content-Type')); }, level: 9 })); //Setting the fav icon and static folder app.use(favicon(config.root + '/public/img/icons/favicon.ico')); app.use(express.static(config.root + '/public')); //Don't use logger for test env if (config.NODE_ENV !== 'test') { app.use(logger('dev', { "stream": winston.stream })); } //Set views path, template engine and default layout app.set('views', config.root + '/app/views'); app.set('view engine', 'jade'); //Enable jsonp app.enable("jsonp callback"); //cookieParser should be above session app.use(cookieParser()); // request body parsing middleware should be above methodOverride app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.json()); app.use(methodOverride()); //express session configuration app.use(sessionMiddleware); //connect flash for flash messages app.use(flash()); //dynamic helpers app.use(helpers(config.app.name)); //use passport session app.use(passport.initialize()); app.use(passport.session()); // Globbing routing files config.getGlobbedFiles('./app/routes/**/*.js').forEach(function(routePath) { require(path.resolve(routePath))(app); }); app.get('*', function (req, res, next) { res.render('index'); }); app.use(function(err, req, res, next) { //Log it winston.error(err); //Error page res.status(500).render('500', { error: err.stack }); }); }; ================================================ FILE: config/middlewares/session.js ================================================ 'use strict'; var config = require('./../config'), session = require('express-session'), db = require('./../sequelize') ; var sequelizeStore = require('express-sequelize-session')(session.Store); var sessionMiddleware = session({ resave: true, saveUninitialized: true, store: new sequelizeStore(db.sequelize), cookie:{maxAge:1000*3600*24*7}, //remember for 7 days secret: config.expressSessionSecret/*||'$uper$ecret$e$$ionKey'*/ }); module.exports = sessionMiddleware; ================================================ FILE: config/passport.js ================================================ 'use strict'; var passport = require('passport'), _ = require('lodash'); // These are different types of authentication strategies that can be used with Passport. var LocalStrategy = require('passport-local').Strategy, FacebookTokenStrategy = require('passport-facebook-token'), config = require('./config'), db = require('./sequelize'), winston = require('./winston'); //Serialize sessions passport.serializeUser(function(user, done) { done(null, user.id); }); passport.deserializeUser(function(id, done) { db.User.find({where: {id: id}}).then(function(user){ if(!user){ winston.warn('Logged in user not in database, user possibly deleted post-login'); return done(null, false); } winston.info('Session: { id: ' + user.id + ', username: ' + user.username + ' }'); done(null, user); }).catch(function(err){ done(err, null); }); }); //Use local strategy passport.use(new LocalStrategy({ usernameField: 'email', passwordField: 'password' }, function(email, password, done) { db.User.find({ where: { email: email }}).then(function(user) { if (!user) { done(null, false, { message: 'Unknown user' }); } else if (!user.authenticate(password)) { done(null, false, { message: 'Invalid password'}); } else { winston.info('Login (local) : { id: ' + user.id + ', username: ' + user.username + ' }'); done(null, user); } }).catch(function(err){ done(err); }); } )); passport.use(new FacebookTokenStrategy({ clientID: config.facebook.clientID, clientSecret: config.facebook.clientSecret, profileFields: ['id', 'first_name', 'last_name', 'email', 'photos'] }, function (accessToken, refreshToken, profile, done) { db.User.find({where : {email: profile.emails[0].value}}).then(function(user){ if(!user){ db.User.create({ name: profile.name.givenName || '', email: profile.emails[0].value, username: profile.name.givenName || '', provider: 'facebook', facebookUserId: profile.id }).then(function(u){ winston.info('New User (facebook) : { id: ' + u.id + ', username: ' + u.username + ' }'); done(null, u); }) } else { winston.info('Login (facebook) : { id: ' + user.id + ', username: ' + user.username + ' }'); done(null, user); } }).catch(function(err){ done(err, null); }); } )); module.exports = passport; ================================================ FILE: config/sequelize.js ================================================ 'use strict'; var fs = require('fs'); var path = require('path'); var Sequelize = require('sequelize'); var _ = require('lodash'); var config = require('./config'); var winston = require('./winston'); var db = {}; winston.info('Initializing Sequelize...'); // create your instance of sequelize var onHeroku = !!process.env.DYNO; winston.info('Checking if running on Heroku: ',onHeroku); var sequelize = onHeroku ? new Sequelize(process.env.DATABASE_URL, { dialect: 'postgres', protocol: 'postgres', dialectOptions: { ssl: true } }) : new Sequelize(config.db.name, config.db.username, config.db.password, { host: config.db.host, port: config.db.port, dialect: 'mysql', storage: config.db.storage, logging: config.enableSequelizeLog === 'true' ? winston.verbose : false }); // loop through all files in models directory ignoring hidden files and this file fs.readdirSync(config.modelsDir) .filter(function (file) { return (file.indexOf('.') !== 0) && (file !== 'index.js') }) // import model files and save model names .forEach(function (file) { winston.info('Loading model file ' + file); var model = sequelize.import(path.join(config.modelsDir, file)); db[model.name] = model; }); // invoke associations on each of the models Object.keys(db).forEach(function (modelName) { if (db[modelName].options.hasOwnProperty('associate')) { db[modelName].options.associate(db) } }); // Synchronizing any model changes with database. // set FORCE_DB_SYNC=true in the environment, or the program parameters to drop the database, // and force model changes into it, if required; // Caution: Do not set FORCE_DB_SYNC to true for every run to avoid losing data with restarts sequelize .sync({ force: config.FORCE_DB_SYNC === 'true', logging: config.enableSequelizeLog === 'true' ? winston.verbose : false }) .then(function () { winston.info("Database " + (config.FORCE_DB_SYNC === 'true' ? "*DROPPED* and " : "") + "synchronized"); }).catch(function (err) { winston.error("An error occurred: ", err); }); // assign the sequelize variables to the db object and returning the db. module.exports = _.extend({ sequelize: sequelize, Sequelize: Sequelize }, db); ================================================ FILE: config/winston.js ================================================ 'use strict'; /** * Created by Junaid Anwar on 5/28/15. */ var winston = require('winston'); var logger = new (winston.Logger)(); logger.add(winston.transports.Console, { level: 'verbose', prettyPrint: true, colorize: true, silent: false, timestamp: false }); logger.stream = { write: function(message, encoding){ logger.info(message); } }; module.exports = logger; ================================================ FILE: gruntfile.js ================================================ 'use strict'; module.exports = function(grunt) { // Project Configuration grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), watch: { jade: { files: ['app/views/**'], options: { livereload: true, }, }, js: { files: ['public/js/**', 'app/**/*.js', 'config/**/*.js'], tasks: ['jshint'], options: { livereload: true, }, }, html: { files: ['public/views/**'], options: { livereload: true, }, }, css: { files: ['public/css/**'], options: { livereload: true } } }, jshint: { all:['gruntfile.js', 'public/js/**/*.js', 'test/mocha/**/*.js', 'test/karma/**/*.js', 'app/**/*.js'], options: { jshintrc: '.jshintrc', reporter: require('jshint-stylish') } }, copy: { options: { punctuation: '' }, js: { files: [ {cwd: 'bower_components/angular-bootstrap', src: ['**/*.js'], dest: 'public/lib/angular-bootstrap', expand: true}, {cwd: 'bower_components/angular-cookies', src: ['angular-cookies*'], dest: 'public/lib/angular-cookies', expand: true}, {cwd: 'bower_components/angular-mocks', src: ['**/*.js'], dest: 'public/lib/angular-mocks', expand: true}, {cwd: 'bower_components/angular-resource', src: ['angular-resource*'], dest: 'public/lib/angular-resource', expand: true}, {cwd: 'bower_components/angular-route', src: ['angular-route*'], dest: 'public/lib/angular-route', expand: true}, {cwd: 'bower_components/angular', src: ['angular*'], dest: 'public/lib/angular', expand: true}, {cwd: 'bower_components/angular-ui-utils/demo', src: ['**/*.js'], dest: 'public/lib/angular-ui-utils/demo', expand: true}, {cwd: 'bower_components/angular-ui-utils/test', src: ['**/*.js'], dest: 'public/lib/angular-ui-utils/test', expand: true}, {cwd: 'bower_components/angular-ui-utils/modules', src: ['**/*.js'], dest: 'public/lib/angular-ui-utils/modules', expand: true}, {cwd: 'bower_components/angular-ui-utils/modules/event', src: ['**/*.js'], dest: 'public/lib/angular-ui-utils/modules/event', expand: true}, {cwd: 'bower_components/angular-ui-utils/modules/format', src: ['**/*.js'], dest: 'public/lib/angular-ui-utils/modules/format', expand: true}, {cwd: 'bower_components/angular-ui-utils/modules/highlight', src: ['**/*.js'], dest: 'public/lib/angular-ui-utils/modules/highlight', expand: true}, {cwd: 'bower_components/angular-ui-utils/modules/ie-shiv', src: ['**/*.js'], dest: 'public/lib/angular-ui-utils/modules/ie-shiv', expand: true}, {cwd: 'bower_components/angular-ui-utils/modules/indeterminate', src: ['**/*.js'], dest: 'public/lib/angular-ui-utils/modules/indeterminate', expand: true}, {cwd: 'bower_components/angular-ui-utils/modules/inflector', src: ['**/*.js'], dest: 'public/lib/angular-ui-utils/modules/inflector', expand: true}, {cwd: 'bower_components/angular-ui-utils/modules/jq', src: ['**/*.js'], dest: 'public/lib/angular-ui-utils/modules/jq', expand: true}, {cwd: 'bower_components/angular-ui-utils/modules/keypress', src: ['**/*.js'], dest: 'public/lib/angular-ui-utils/modules/keypress', expand: true}, {cwd: 'bower_components/angular-ui-utils/modules/mask', src: ['**/*.js'], dest: 'public/lib/angular-ui-utils/modules/mask', expand: true}, {cwd: 'bower_components/angular-ui-utils/modules/reset', src: ['**/*.js'], dest: 'public/lib/angular-ui-utils/modules/reset', expand: true}, {cwd: 'bower_components/angular-ui-utils/modules/reset/stylesheets', src: ['**/*.js'], dest: 'public/lib/angular-ui-utils/modules/reset/stylesheets', expand: true}, {cwd: 'bower_components/angular-ui-utils/modules/route', src: ['**/*.js'], dest: 'public/lib/angular-ui-utils/modules/route', expand: true}, {cwd: 'bower_components/angular-ui-utils/modules/scrollfix', src: ['**/*.js'], dest: 'public/lib/angular-ui-utils/modules/scrollfix', expand: true}, {cwd: 'bower_components/angular-ui-utils/modules/showhide', src: ['**/*.js'], dest: 'public/lib/angular-ui-utils/modules/showhide', expand: true}, {cwd: 'bower_components/angular-ui-utils/modules/unique', src: ['**/*.js'], dest: 'public/lib/angular-ui-utils/modules/unique', expand: true}, {cwd: 'bower_components/angular-ui-utils/modules/validate', src: ['**/*.js'], dest: 'public/lib/angular-ui-utils/modules/validate', expand: true}, {cwd: 'bower_components/bootstrap/js', src: ['*.js'], dest: 'public/lib/bootstrap/js', expand: true}, {cwd: 'bower_components/bootstrap/less', src: ['*.less'], dest: 'public/lib/bootstrap/less', expand: true}, {cwd: 'bower_components/bootstrap/docs/assets/css', src: ['*.*'], dest: 'public/lib/bootstrap/docs/assets/css', expand: true}, {cwd: 'bower_components/bootstrap/docs/assets/ico', src: ['*.*'], dest: 'public/lib/bootstrap/docs/assets/ico', expand: true}, {cwd: 'bower_components/bootstrap/docs/assets/img', src: ['*.*'], dest: 'public/lib/bootstrap/docs/assets/img', expand: true}, {cwd: 'bower_components/bootstrap/docs/assets/js', src: ['*.*'], dest: 'public/lib/bootstrap/docs/assets/js', expand: true}, {cwd: 'bower_components/satellizer/dist', src: ['**/*.js'], dest: 'public/lib/satellizer', expand: true}, {cwd: 'bower_components/angular-fblogin/dist', src: ['**/*.js'], dest: 'public/lib/social', expand: true}, {cwd: 'bower_components/jquery', src: ['jquery*'], dest: 'public/lib/jquery', expand: true}, {cwd: 'bower_components/angular-ui-router', src: ['release/*.js'], dest: 'public/lib/angular-ui-router', expand: true} ] } }, nodemon: { dev: { script: 'app.js', options: { args: ['--color'], ignore: ['README.md', 'node_modules/**', '.DS_Store'], ext: 'js', watch: ['app', 'config', 'app.js', 'gruntfile.js'], delay: 1000, env: { PORT: 3000 }, cwd: __dirname } } }, concurrent: { tasks: ['nodemon', 'watch'], options: { logConcurrentOutput: true } }, mochaTest: { options: { reporter: 'spec' }, src: ['test/mocha/**/*.js'] }, env: { test: { NODE_ENV: 'test' } }, karma: { unit: { configFile: 'test/karma/karma.conf.js' } } }); //Load NPM tasks grunt.loadNpmTasks('grunt-contrib-watch'); grunt.loadNpmTasks('grunt-contrib-jshint'); grunt.loadNpmTasks('grunt-contrib-nodemon'); grunt.loadNpmTasks('grunt-concurrent'); grunt.loadNpmTasks('grunt-mocha-test'); grunt.loadNpmTasks('grunt-karma'); grunt.loadNpmTasks('grunt-env'); grunt.loadNpmTasks('grunt-copy'); //Making grunt default to force in order not to break the project. grunt.option('force', true); //Default task(s). grunt.registerTask('default', ['copy', 'jshint', 'concurrent']); //Test task. grunt.registerTask('test', ['env:test', 'mochaTest', 'karma:unit']); }; ================================================ FILE: package.json ================================================ { "name": "mean-stack-relational", "description": "M*EAN - A Modern Stack: MySQL(Postgres for Heroku), ExpressJS, AngularJS, NodeJS. (BONUS: Passport User Support).", "version": "0.2.2", "private": false, "author": "Jeff Potter", "license": "MIT", "repository": { "type": "git", "url": "https://github.com/jpotts18/mean-stack-relational.git" }, "engines": { "node": "4.2.x", "npm": "2.14.x" }, "scripts": { "start": "node node_modules/grunt-cli/bin/grunt", "test": "node node_modules/grunt-cli/bin/grunt test", "postinstall": "bower install && cp config/env/development.json5.sample config/env/development.json5 && cp config/env/production.json5.sample config/env/production.json5 && node node_modules/grunt-cli/bin/grunt copy" }, "dependencies": { "async": "latest", "body-parser": "^1.14.1", "bower": "latest", "compression": "^1.6.0", "connect-flash": "^0.1.1", "cookie-parser": "^1.4.0", "express": "^4.13.3", "express-sequelize-session": "^0.4.0", "express-session": "^1.12.1", "glob": "^6.0.1", "grunt": "^0.4.5", "grunt-cli": "^0.1.13", "grunt-copy": "latest", "grunt-env": "latest", "jade": "^1.11.0", "jshint-stylish": "^2.1.0", "json5": "^0.4.0", "lodash": "latest", "method-override": "^2.3.5", "morgan": "^1.6.1", "mysql": "^2.9.0", "nconf": "^0.8.2", "passport": "^0.3.2", "passport-facebook": "^2.0.0", "passport-google": "latest", "passport-local": "^1.0.0", "passport-twitter": "^1.0.3", "pg": "^4.4.3", "pm2": "^1.0.0", "proxyquire": "^1.7.3", "sequelize": "^3.13.0", "serve-favicon": "^2.3.0", "standard-error": "^1.1.0", "view-helpers": "latest", "winston": "latest", "request": "2.75.0", "qs": "6.2.1", "passport-facebook-token": "3.3.0" }, "devDependencies": { "chai": "^3.2.0", "grunt-concurrent": "latest", "grunt-contrib-jshint": "latest", "grunt-contrib-nodemon": "^0.5.2", "grunt-contrib-watch": "latest", "grunt-karma": "~0.6.2", "grunt-mocha-test": "latest", "karma": "~0.10.4", "karma-chrome-launcher": "~0.1.0", "karma-coffee-preprocessor": "~0.1.0", "karma-coverage": "~0.1.0", "karma-firefox-launcher": "~0.1.0", "karma-html2js-preprocessor": "~0.1.0", "karma-jasmine": "~0.1.3", "karma-phantomjs-launcher": "~0.1.0", "karma-requirejs": "~0.2.0", "karma-script-launcher": "~0.1.0", "mocha": "^2.2.5", "supertest": "latest" } } ================================================ FILE: pm2-ecosystem.json ================================================ { "apps" : [{ // mean-stack-relational app "name" : "msr", "script" : "app.js", // by default, the app runs in fork mode, uncomment below to run in cluster mode //"instances" : 4, //"exec_mode" : "cluster_mode", // defaults to fork "args" : ["--color"], "watch" : true, "ignore_watch" : ["pids", "logs", "node_modules", "bower_components"], "merge_logs" : true, // merge logs from all instances in cluster mode "cwd" : ".", "error_file" : "./logs/msr.log", "out_file" : "./logs/msr.log", "pid_file" : "./pids/msr.pid", "min_uptime" : "30s", // defaults to 1000s "max_restarts" : 30, // defaults to 15 "restart_delay" : 1000, "max_memory_restart" : "8G", // restart app if it reaches an 8GB memory footprint "env": { "NODE_ENV": "development" }, "env_production" : { "NODE_ENV": "production" } }] } ================================================ FILE: pm2-main.js ================================================ var pm2 = require('pm2'); var instances = process.env.WEB_CONCURRENCY || 1; // Set by Heroku or 1 (set -1 to scale to max cpu core - 1) var maxMemory = process.env.WEB_MEMORY || 512; // 512 is the maximum free Dyno memory on Heroku pm2.connect(function() { pm2.start({ script : 'app.js', name : 'msr', // ----> THESE ATTRIBUTES ARE OPTIONAL: exec_mode : 'cluster', // set to 'cluster' for cluster execution instances : instances, max_memory_restart : maxMemory + 'M', // Auto restart if process taking more than XXmo env: { // If needed declare some environment variables "NODE_ENV": "production" } }, function(err) { if (err) return console.error('Error while launching applications', err.stack || err); console.log('PM2 and application has been successfully started'); // Display logs in standard output pm2.launchBus(function(err, bus) { console.log('[PM2] Log streaming started'); bus.on('log:out', function(packet) { console.log('[App:%s] %s', packet.process.name, packet.data); }); bus.on('log:err', function(packet) { console.error('[App:%s][Err] %s', packet.process.name, packet.data); }); }); }); }); ================================================ FILE: public/css/common.css ================================================ .navbar .nav>li>a.brand { padding-left:20px; margin-left:0 } .content { margin-top:50px; width:100% } footer { position:fixed; left:0px; bottom:0px; height:30px; width:100%; background:#ddd; -webkit-box-shadow:0 8px 6px 6px black; -moz-box-shadow:0 8px 6px 6px black; box-shadow:0 8px 6px 6px black } footer p { padding:5px 0 12px 10px } ================================================ FILE: public/css/views/articles.css ================================================ h1 { text-align:center } ul.articles li:not(:last-child) { border-bottom:1px solid #ccc } ================================================ FILE: public/humans.txt ================================================ # humanstxt.org/ # The humans responsible & technology colophon # TEAM -- -- # THANKS # TECHNOLOGY COLOPHON HTML5, CSS3 jQuery, Modernizr ================================================ FILE: public/img/.gitignore ================================================ ================================================ FILE: public/js/FbSdk.js ================================================ /*1453279404,,JIT Construction: v2136283,en_US*/ /** * Copyright Facebook Inc. * * Licensed under the Apache License, Version 2.0 * http://www.apache.org/licenses/LICENSE-2.0 */ try { window.FB || (function (window, fb_fif_window) { var apply = Function.prototype.apply; function bindContext(fn, thisArg) { return function _sdkBound() { return apply.call(fn, thisArg, arguments); }; } var global = {__type: 'JS_SDK_SANDBOX', window: window, document: window.document}; var sandboxWhitelist = ['setTimeout', 'setInterval', 'clearTimeout', 'clearInterval']; for (var i = 0; i < sandboxWhitelist.length; i++) { global[sandboxWhitelist[i]] = bindContext(window[sandboxWhitelist[i]], window); } (function () { var self = window; var __DEV__ = 0; function emptyFunction() { } var __transform_includes = {}; var __annotator, __bodyWrapper; var __w, __t; var undefined; with (this) { (function () { var a = {}, b = function (i, j) { if (!i && !j)return null; var k = {}; if (typeof i !== 'undefined')k.type = i; if (typeof j !== 'undefined')k.signature = j; return k; }, c = function (i, j) { return b(i && /^[A-Z]/.test(i) ? i : undefined, j && (j.params && j.params.length || j.returns) ? 'function(' + (j.params ? j.params.map(function (k) { return (/\?/.test(k) ? '?' + k.replace('?', '') : k); }).join(',') : '') + ')' + (j.returns ? ':' + j.returns : '') : undefined); }, d = function (i, j, k) { return i; }, e = function (i, j, k) { if ('sourcemeta' in __transform_includes)i.__SMmeta = j; if ('typechecks' in __transform_includes) { var l = c(j ? j.name : undefined, k); if (l)__w(i, l); } return i; }, f = function (i, j, k) { return k.apply(i, j); }, g = function (i, j, k, l) { if (l && l.params)__t.apply(i, l.params); var m = k.apply(i, j); if (l && l.returns)__t([m, l.returns]); return m; }, h = function (i, j, k, l, m) { if (m) { if (!m.callId)m.callId = m.module + ':' + (m.line || 0) + ':' + (m.column || 0); var n = m.callId; a[n] = (a[n] || 0) + 1; } return k.apply(i, j); }; if (typeof __transform_includes === 'undefined') { __annotator = d; __bodyWrapper = f; } else { __annotator = e; if ('codeusage' in __transform_includes) { __annotator = d; __bodyWrapper = h; __bodyWrapper.getCodeUsage = function () { return a; }; __bodyWrapper.clearCodeUsage = function () { a = {}; }; } else if ('typechecks' in __transform_includes) { __bodyWrapper = g; } else __bodyWrapper = f; } })(); __t=function(a){return a[0];};__w=function(a){return a;}; var require,__d;(function(a){var b={},c={},d=['global','require','requireDynamic','requireLazy','module','exports'];require=function(e,f){if(c.hasOwnProperty(e))return c[e];if(!b.hasOwnProperty(e)){if(f)return null;throw new Error('Module '+e+' has not been defined');}var g=b[e],h=g.deps,i=g.factory.length,j,k=[];for(var l=0;l1?Number(arguments[1]):0;if(isNaN(k))k=0;var l=Math.min(Math.max(k,0),j.length);return j.indexOf(String(i),k)==l;};h.endsWith=function(i){var j=String(this);if(this==null)throw new TypeError('String.prototype.endsWith called on null or undefined');var k=j.length,l=String(i),m=arguments.length>1?Number(arguments[1]):k;if(isNaN(m))m=0;var n=Math.min(Math.max(m,0),k),o=n-l.length;if(o<0)return false;return j.lastIndexOf(l,o)==o;};h.contains=function(i){if(this==null)throw new TypeError('String.prototype.contains called on null or undefined');var j=String(this),k=arguments.length>1?Number(arguments[1]):0;if(isNaN(k))k=0;return j.indexOf(String(i),k)!=-1;};h.repeat=function(i){if(this==null)throw new TypeError('String.prototype.repeat called on null or undefined');var j=String(this),k=i?Number(i):0;if(isNaN(k))k=0;if(k<0||k===Infinity)throw RangeError();if(k===1)return j;if(k===0)return '';var l='';while(k){if(k&1)l+=j;if(k>>=1)j+=j;}return l;};f.exports=h;},null); __d('ES6Array',[],function a(b,c,d,e,f,g){'use strict';if(c.__markCompiled)c.__markCompiled();var h={from:function(i){if(i==null)throw new TypeError('Object is null or undefined');var j=arguments[1],k=arguments[2],l=this,m=Object(i),n=typeof Symbol==='function'?typeof Symbol==='function'?Symbol.iterator:'@@iterator':'@@iterator',o=typeof j==='function',p=typeof m[n]==='function',q=0,r,s;if(p){r=typeof l==='function'?new l():[];var t=m[n](),u;while(!(u=t.next()).done){s=u.value;if(o)s=j.call(k,s,q);r[q]=s;q+=1;}r.length=q;return r;}var v=m.length;if(isNaN(v)||v<0)v=0;r=typeof l==='function'?new l(v):new Array(v);while(q>>0;for(var m=0;m>>0,l=arguments[1],m=l>>0,n=m<0?Math.max(k+m,0):Math.min(m,k),o=arguments[2],p=o===undefined?k:o>>0,q=p<0?Math.max(k+p,0):Math.min(p,k);while(n9999?'+':'')+('00000'+Math.abs(j)).slice(0<=j&&j<=9999?-4:-6);return j+'-'+h(this.getUTCMonth()+1)+'-'+h(this.getUTCDate())+'T'+h(this.getUTCHours())+':'+h(this.getUTCMinutes())+':'+h(this.getUTCSeconds())+'.'+(this.getUTCMilliseconds()/1000).toFixed(3).slice(2,5)+'Z';}};f.exports=i;},null); __d('ES6Number',[],function a(b,c,d,e,f,g){if(c.__markCompiled)c.__markCompiled();var h=Math.pow(2,-52),i=Math.pow(2,53)-1,j=-1*i,k={isFinite:function(l){return typeof l=='number'&&isFinite(l);},isNaN:function(l){return typeof l=='number'&&isNaN(l);},isInteger:function(l){return this.isFinite(l)&&Math.floor(l)===l;},isSafeInteger:function(l){return this.isFinite(l)&&l>=this.MIN_SAFE_INTEGER&&l<=this.MAX_SAFE_INTEGER&&Math.floor(l)===l;},EPSILON:h,MAX_SAFE_INTEGER:i,MIN_SAFE_INTEGER:j};f.exports=k;},null); __d('ES6Object',['ie8DontEnum'],function a(b,c,d,e,f,g,h){if(c.__markCompiled)c.__markCompiled();var i=({}).hasOwnProperty,j={assign:function(k){if(k==null)throw new TypeError('Object.assign target cannot be null or undefined');k=Object(k);for(var l=arguments.length,m=Array(l>1?l-1:0),n=1;n1)))/4)-da((ha-1901+ia)/100)+da((ha-1601+ia)/400);};}if(typeof JSON=="object"&&JSON){l.stringify=JSON.stringify;l.parse=JSON.parse;}if((n=typeof l.stringify=="function"&&!fa)){(ca=function(){return 1;}).toJSON=ca;try{n=l.stringify(0)==="0"&&l.stringify(Number())==="0"&&l.stringify(String())=='""'&&l.stringify(h)===k&&l.stringify(k)===k&&l.stringify()===k&&l.stringify(ca)==="1"&&l.stringify([ca])=="[1]"&&l.stringify([k])=="[null]"&&l.stringify(null)=="null"&&l.stringify([k,h,null])=="[null,null,null]"&&l.stringify({result:[ca,true,false,null,"\0\b\n\f\r\t"]})==m&&l.stringify(null,ca)==="1"&&l.stringify([1,2],null,1)=="[\n 1,\n 2\n]"&&l.stringify(new Date(-8.64e+15))=='"-271821-04-20T00:00:00.000Z"'&&l.stringify(new Date(8.64e+15))=='"+275760-09-13T00:00:00.000Z"'&&l.stringify(new Date(-62198755200000))=='"-000001-01-01T00:00:00.000Z"'&&l.stringify(new Date(-1))=='"1969-12-31T23:59:59.999Z"';}catch(ga){n=false;}}if(typeof l.parse=="function")try{if(l.parse("0")===0&&!l.parse(false)){ca=l.parse(m);if((s=ca.A.length==5&&ca.A[0]==1)){try{s=!l.parse('"\t"');}catch(ga){}if(s)try{s=l.parse("01")!=1;}catch(ga){}}}}catch(ga){s=false;}ca=m=null;if(!n||!s){if(!(i={}.hasOwnProperty))i=function(ha){var ia={},ja;if((ia.__proto__=null,ia.__proto__={toString:1},ia).toString!=h){i=function(ka){var la=this.__proto__,ma=ka in (this.__proto__=null,this);this.__proto__=la;return ma;};}else{ja=ia.constructor;i=function(ka){var la=(this.constructor||ja).prototype;return ka in this&&!(ka in la&&this[ka]===la[ka]);};}ia=null;return i.call(this,ha);};j=function(ha,ia){var ja=0,ka,la,ma,na;(ka=function(){this.valueOf=0;}).prototype.valueOf=0;la=new ka();for(ma in la)if(i.call(la,ma))ja++;ka=la=null;if(!ja){la=["valueOf","toString","toLocaleString","propertyIsEnumerable","isPrototypeOf","hasOwnProperty","constructor"];na=function(oa,pa){var qa=h.call(oa)=="[object Function]",ra,sa;for(ra in oa)if(!(qa&&ra=="prototype")&&i.call(oa,ra))pa(ra);for(sa=la.length;ra=la[--sa];i.call(oa,ra)&&pa(ra));};}else if(ja==2){na=function(oa,pa){var qa={},ra=h.call(oa)=="[object Function]",sa;for(sa in oa)if(!(ra&&sa=="prototype")&&!i.call(qa,sa)&&(qa[sa]=1)&&i.call(oa,sa))pa(sa);};}else na=function(oa,pa){var qa=h.call(oa)=="[object Function]",ra,sa;for(ra in oa)if(!(qa&&ra=="prototype")&&i.call(oa,ra)&&!(sa=ra==="constructor"))pa(ra);if(sa||i.call(oa,(ra="constructor")))pa(ra);};return na(ha,ia);};if(!n){o={"\\":"\\\\",'"':'\\"',"\b":"\\b","\f":"\\f","\n":"\\n","\r":"\\r","\t":"\\t"};p=function(ha,ia){return ("000000"+(ia||0)).slice(-ha);};q=function(ha){var ia='"',ja=0,ka;for(;ka=ha.charAt(ja);ja++)ia+='\\"\b\f\n\r\t'.indexOf(ka)>-1?o[ka]:ka<" "?"\\u00"+p(2,ka.charCodeAt(0).toString(16)):ka;return ia+'"';};r=function(ha,ia,ja,ka,la,ma,na){var oa=ia[ha],pa,qa,ra,sa,ta,ua,va,wa,xa,ya,za,ab,bb,cb,db;if(typeof oa=="object"&&oa){pa=h.call(oa);if(pa=="[object Date]"&&!i.call(oa,"toJSON")){if(oa>-1/0&&oa<1/0){if(fa){sa=da(oa/86400000);for(qa=da(sa/365.2425)+1970-1;fa(qa+1,0)<=sa;qa++);for(ra=da((sa-fa(qa,0))/30.42);fa(qa,ra+1)<=sa;ra++);sa=1+sa-fa(qa,ra);ta=(oa%86400000+86400000)%86400000;ua=da(ta/3600000)%24;va=da(ta/60000)%60;wa=da(ta/1000)%60;xa=ta%1000;}else{qa=oa.getUTCFullYear();ra=oa.getUTCMonth();sa=oa.getUTCDate();ua=oa.getUTCHours();va=oa.getUTCMinutes();wa=oa.getUTCSeconds();xa=oa.getUTCMilliseconds();}oa=(qa<=0||qa>=10000?(qa<0?"-":"+")+p(6,qa<0?-qa:qa):p(4,qa))+"-"+p(2,ra+1)+"-"+p(2,sa)+"T"+p(2,ua)+":"+p(2,va)+":"+p(2,wa)+"."+p(3,xa)+"Z";}else oa=null;}else if(typeof oa.toJSON=="function"&&((pa!="[object Number]"&&pa!="[object String]"&&pa!="[object Array]")||i.call(oa,"toJSON")))oa=oa.toJSON(ha);}if(ja)oa=ja.call(ia,ha,oa);if(oa===null)return "null";pa=h.call(oa);if(pa=="[object Boolean]"){return ""+oa;}else if(pa=="[object Number]"){return oa>-1/0&&oa<1/0?""+oa:"null";}else if(pa=="[object String]")return q(oa);if(typeof oa=="object"){for(bb=na.length;bb--;)if(na[bb]===oa)throw TypeError();na.push(oa);ya=[];cb=ma;ma+=la;if(pa=="[object Array]"){for(ab=0,bb=oa.length;ab0)for(ka="",ja>10&&(ja=10);ka.length-1){aa++;}else if("{}[]:,".indexOf(ja)>-1){aa++;return ja;}else if(ja=='"'){for(ka="@",aa++;aa-1){ka+=u[ja];aa++;}else if(ja=="u"){la=++aa;for(ma=aa+4;aa="0"&&ja<="9"||ja>="a"&&ja<="f"||ja>="A"&&ja<="F"))v();}ka+=t("0x"+ha.slice(la,aa));}else v();}else{if(ja=='"')break;ka+=ja;aa++;}}if(ha.charAt(aa)=='"'){aa++;return ka;}v();}else{la=aa;if(ja=="-"){na=true;ja=ha.charAt(++aa);}if(ja>="0"&&ja<="9"){if(ja=="0"&&(ja=ha.charAt(aa+1),ja>="0"&&ja<="9"))v();na=false;for(;aa="0"&&ja<="9");aa++);if(ha.charAt(aa)=="."){ma=++aa;for(;ma="0"&&ja<="9");ma++);if(ma==aa)v();aa=ma;}ja=ha.charAt(aa);if(ja=="e"||ja=="E"){ja=ha.charAt(++aa);if(ja=="+"||ja=="-")aa++;for(ma=aa;ma="0"&&ja<="9");ma++);if(ma==aa)v();aa=ma;}return +ha.slice(la,aa);}if(na)v();if(ha.slice(aa,aa+4)=="true"){aa+=4;return true;}else if(ha.slice(aa,aa+5)=="false"){aa+=5;return false;}else if(ha.slice(aa,aa+4)=="null"){aa+=4;return null;}v();}}return "$";};x=function(ha){var ia,ja,ka;if(ha=="$")v();if(typeof ha=="string"){if(ha.charAt(0)=="@")return ha.slice(1);if(ha=="["){ia=[];for(;;ja||(ja=true)){ha=w();if(ha=="]")break;if(ja)if(ha==","){ha=w();if(ha=="]")v();}else v();if(ha==",")v();ia.push(x(ha));}return ia;}else if(ha=="{"){ia={};for(;;ja||(ja=true)){ha=w();if(ha=="}")break;if(ja)if(ha==","){ha=w();if(ha=="}")v();}else v();if(ha==","||typeof ha!="string"||ha.charAt(0)!="@"||w()!=":")v();ia[ha.slice(1)]=x(w());}return ia;}v();}return ha;};z=function(ha,ia,ja){var ka=y(ha,ia,ja);if(ka===k){delete ha[ia];}else ha[ia]=ka;};y=function(ha,ia,ja){var ka=ha[ia],la;if(typeof ka=="object"&&ka)if(h.call(ka)=="[object Array]"){for(la=ka.length;la--;)z(ka,la,ja);}else j(ka,function(ma){z(ka,ma,ja);});return ja.call(ha,ia,ka);};l.parse=function(ha,ia){aa=0;ba=ha;var ja=x(w());if(w()!="$")v();aa=ba=null;return ia&&h.call(ia)=="[object Function]"?y((ca={},ca[""]=ja,ca),"",ia):ja;};}}}).call(this);},null); __d('ES',['ES5ArrayPrototype','ES5FunctionPrototype','ES5StringPrototype','ES5Array','ES5Object','ES5Date','JSON3','ES6Array','ES6Object','ES6ArrayPrototype','ES6DatePrototype','ES6Number','ES7StringPrototype'],function a(b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t){if(c.__markCompiled)c.__markCompiled();var u=({}).toString,v={'JSON.stringify':n.stringify,'JSON.parse':n.parse},w={'Array.prototype':h,'Function.prototype':i,'String.prototype':j,Object:l,Array:k,Date:m},x={Object:p,'Array.prototype':q,'Date.prototype':r,Number:s,Array:o},y={'String.prototype':t};function z(ba){for(var ca in ba){if(!ba.hasOwnProperty(ca))continue;var da=ba[ca],ea=ca.split('.'),fa=ea.length==2?window[ea[0]][ea[1]]:window[ca];for(var ga in da){if(!da.hasOwnProperty(ga))continue;if(typeof da[ga]!=='function'){v[ca+'.'+ga]=da[ga];continue;}var ha=fa[ga];v[ca+'.'+ga]=ha&&/\{\s+\[native code\]\s\}/.test(ha)?ha:da[ga];}}}z(w);z(x);z(y);function aa(ba,ca,da){var ea=da?u.call(ba).slice(8,-1)+'.prototype':ba,fa=v[ea+'.'+ca]||ba[ca];if(typeof fa==='function'){for(var ga=arguments.length,ha=Array(ga>3?ga-3:0),ia=3;ia=0)continue;o[p]=m[p];}return o;};k.taggedTemplateLiteralLoose=function(m,n){m.raw=n;return m;};k.bind=h.bind;f.exports=k;},null); var ES = require('ES'); var babelHelpers = require('sdk.babelHelpers'); __d("UrlMapConfig",[],{"www":"www.facebook.com","m":"m.facebook.com","connect":"connect.facebook.net","business":"business.facebook.com","api_https":"api.facebook.com","api_read_https":"api-read.facebook.com","graph_https":"graph.facebook.com","fbcdn_http":"fbstatic-a.akamaihd.net","fbcdn_https":"fbstatic-a.akamaihd.net","cdn_http":"staticxx.facebook.com","cdn_https":"staticxx.facebook.com"});__d("JSSDKRuntimeConfig",[],{"locale":"en_US","rtl":false,"revision":"2136283"});__d("JSSDKConfig",[],{"bustCache":true,"tagCountLogRate":0.01,"errorHandling":{"rate":4},"usePluginPipe":true,"features":{"dialog_resize_refactor":true,"one_comment_controller":true,"allow_non_canvas_app_events":false,"event_subscriptions_log":{"rate":0.01,"value":10000},"should_force_single_dialog_instance":true,"js_sdk_force_status_on_load":true,"kill_fragment":true,"xfbml_profile_pic_server":true,"error_handling":{"rate":4},"e2e_ping_tracking":{"rate":1.0e-6},"getloginstatus_tracking":{"rate":0.001},"xd_timeout":{"rate":4,"value":30000},"use_bundle":false,"launch_payment_dialog_via_pac":{"rate":100},"plugin_tags_blacklist":["recommendations_bar","registration","activity","recommendations","facepile"],"should_log_response_error":true},"api":{"mode":"warn","whitelist":["AppEvents","AppEvents.EventNames","AppEvents.ParameterNames","AppEvents.activateApp","AppEvents.logEvent","AppEvents.logPurchase","Canvas","Canvas.Prefetcher","Canvas.Prefetcher.addStaticResource","Canvas.Prefetcher.setCollectionMode","Canvas.getPageInfo","Canvas.hideFlashElement","Canvas.scrollTo","Canvas.setAutoGrow","Canvas.setDoneLoading","Canvas.setSize","Canvas.setUrlHandler","Canvas.showFlashElement","Canvas.startTimer","Canvas.stopTimer","Event","Event.subscribe","Event.unsubscribe","Music.flashCallback","Music.init","Music.send","Payment","Payment.cancelFlow","Payment.continueFlow","Payment.init","Payment.lockForProcessing","Payment.parse","Payment.setSize","Payment.unlockForProcessing","ThirdPartyProvider","ThirdPartyProvider.init","ThirdPartyProvider.sendData","UA","UA.nativeApp","XFBML","XFBML.RecommendationsBar","XFBML.RecommendationsBar.markRead","XFBML.parse","addFriend","api","getAccessToken","getAuthResponse","getLoginStatus","getUserID","init","login","logout","publish","share","ui"]},"initSitevars":{"enableMobileComments":1,"iframePermissions":{"read_stream":false,"manage_mailbox":false,"manage_friendlists":false,"read_mailbox":false,"publish_checkins":true,"status_update":true,"photo_upload":true,"video_upload":true,"sms":false,"create_event":true,"rsvp_event":true,"offline_access":true,"email":true,"xmpp_login":false,"create_note":true,"share_item":true,"export_stream":false,"publish_stream":true,"publish_likes":true,"ads_management":false,"contact_email":true,"access_private_data":false,"read_insights":false,"read_requests":false,"read_friendlists":true,"manage_pages":false,"physical_login":false,"manage_groups":false,"read_deals":false}}});__d("JSSDKXDConfig",[],{"XdUrl":"\/connect\/xd_arbiter.php?version=42","XdBundleUrl":"\/connect\/xd_arbiter\/r\/MHETSQlw-F9.js?version=42","Flash":{"path":"https:\/\/connect.facebook.net\/rsrc.php\/v1\/yW\/r\/yOZN1vHw3Z_.swf"},"useCdn":true});__d("JSSDKCssConfig",[],{"rules":".fb_hidden{position:absolute;top:-10000px;z-index:10001}.fb_reposition{overflow-x:hidden;position:relative}.fb_invisible{display:none}.fb_reset{background:none;border:0;border-spacing:0;color:#000;cursor:auto;direction:ltr;font-family:\"lucida grande\", tahoma, verdana, arial, sans-serif;font-size:11px;font-style:normal;font-variant:normal;font-weight:normal;letter-spacing:normal;line-height:1;margin:0;overflow:visible;padding:0;text-align:left;text-decoration:none;text-indent:0;text-shadow:none;text-transform:none;visibility:visible;white-space:normal;word-spacing:normal}.fb_reset>div{overflow:hidden}.fb_link img{border:none}\n.fb_dialog{background:rgba(82, 82, 82, .7);position:absolute;top:-10000px;z-index:10001}.fb_reset .fb_dialog_legacy{overflow:visible}.fb_dialog_advanced{padding:10px;-moz-border-radius:8px;-webkit-border-radius:8px;border-radius:8px}.fb_dialog_content{background:#fff;color:#333}.fb_dialog_close_icon{background:url(https:\/\/fbstatic-a.akamaihd.net\/rsrc.php\/v2\/yq\/r\/IE9JII6Z1Ys.png) no-repeat scroll 0 0 transparent;_background-image:url(https:\/\/fbstatic-a.akamaihd.net\/rsrc.php\/v2\/yL\/r\/s816eWC-2sl.gif);cursor:pointer;display:block;height:15px;position:absolute;right:18px;top:17px;width:15px}.fb_dialog_mobile .fb_dialog_close_icon{top:5px;left:5px;right:auto}.fb_dialog_padding{background-color:transparent;position:absolute;width:1px;z-index:-1}.fb_dialog_close_icon:hover{background:url(https:\/\/fbstatic-a.akamaihd.net\/rsrc.php\/v2\/yq\/r\/IE9JII6Z1Ys.png) no-repeat scroll 0 -15px transparent;_background-image:url(https:\/\/fbstatic-a.akamaihd.net\/rsrc.php\/v2\/yL\/r\/s816eWC-2sl.gif)}.fb_dialog_close_icon:active{background:url(https:\/\/fbstatic-a.akamaihd.net\/rsrc.php\/v2\/yq\/r\/IE9JII6Z1Ys.png) no-repeat scroll 0 -30px transparent;_background-image:url(https:\/\/fbstatic-a.akamaihd.net\/rsrc.php\/v2\/yL\/r\/s816eWC-2sl.gif)}.fb_dialog_loader{background-color:#f6f7f8;border:1px solid #606060;font-size:24px;padding:20px}.fb_dialog_top_left,.fb_dialog_top_right,.fb_dialog_bottom_left,.fb_dialog_bottom_right{height:10px;width:10px;overflow:hidden;position:absolute}.fb_dialog_top_left{background:url(https:\/\/fbstatic-a.akamaihd.net\/rsrc.php\/v2\/ye\/r\/8YeTNIlTZjm.png) no-repeat 0 0;left:-10px;top:-10px}.fb_dialog_top_right{background:url(https:\/\/fbstatic-a.akamaihd.net\/rsrc.php\/v2\/ye\/r\/8YeTNIlTZjm.png) no-repeat 0 -10px;right:-10px;top:-10px}.fb_dialog_bottom_left{background:url(https:\/\/fbstatic-a.akamaihd.net\/rsrc.php\/v2\/ye\/r\/8YeTNIlTZjm.png) no-repeat 0 -20px;bottom:-10px;left:-10px}.fb_dialog_bottom_right{background:url(https:\/\/fbstatic-a.akamaihd.net\/rsrc.php\/v2\/ye\/r\/8YeTNIlTZjm.png) no-repeat 0 -30px;right:-10px;bottom:-10px}.fb_dialog_vert_left,.fb_dialog_vert_right,.fb_dialog_horiz_top,.fb_dialog_horiz_bottom{position:absolute;background:#525252;filter:alpha(opacity=70);opacity:.7}.fb_dialog_vert_left,.fb_dialog_vert_right{width:10px;height:100\u0025}.fb_dialog_vert_left{margin-left:-10px}.fb_dialog_vert_right{right:0;margin-right:-10px}.fb_dialog_horiz_top,.fb_dialog_horiz_bottom{width:100\u0025;height:10px}.fb_dialog_horiz_top{margin-top:-10px}.fb_dialog_horiz_bottom{bottom:0;margin-bottom:-10px}.fb_dialog_iframe{line-height:0}.fb_dialog_content .dialog_title{background:#6d84b4;border:1px solid #3a5795;color:#fff;font-size:14px;font-weight:bold;margin:0}.fb_dialog_content .dialog_title>span{background:url(https:\/\/fbstatic-a.akamaihd.net\/rsrc.php\/v2\/yd\/r\/Cou7n-nqK52.gif) no-repeat 5px 50\u0025;float:left;padding:5px 0 7px 26px}body.fb_hidden{-webkit-transform:none;height:100\u0025;margin:0;overflow:visible;position:absolute;top:-10000px;left:0;width:100\u0025}.fb_dialog.fb_dialog_mobile.loading{background:url(https:\/\/fbstatic-a.akamaihd.net\/rsrc.php\/v2\/ya\/r\/3rhSv5V8j3o.gif) white no-repeat 50\u0025 50\u0025;min-height:100\u0025;min-width:100\u0025;overflow:hidden;position:absolute;top:0;z-index:10001}.fb_dialog.fb_dialog_mobile.loading.centered{width:auto;height:auto;min-height:initial;min-width:initial;background:none}.fb_dialog.fb_dialog_mobile.loading.centered #fb_dialog_loader_spinner{width:100\u0025}.fb_dialog.fb_dialog_mobile.loading.centered .fb_dialog_content{background:none}.loading.centered #fb_dialog_loader_close{color:#fff;display:block;padding-top:20px;clear:both;font-size:18px}#fb-root #fb_dialog_ipad_overlay{background:rgba(0, 0, 0, .45);position:absolute;left:0;top:0;width:100\u0025;min-height:100\u0025;z-index:10000}#fb-root #fb_dialog_ipad_overlay.hidden{display:none}.fb_dialog.fb_dialog_mobile.loading iframe{visibility:hidden}.fb_dialog_content .dialog_header{-webkit-box-shadow:white 0 1px 1px -1px inset;background:-webkit-gradient(linear, 0\u0025 0\u0025, 0\u0025 100\u0025, from(#738ABA), to(#2C4987));border-bottom:1px solid;border-color:#1d4088;color:#fff;font:14px Helvetica, sans-serif;font-weight:bold;text-overflow:ellipsis;text-shadow:rgba(0, 30, 84, .296875) 0 -1px 0;vertical-align:middle;white-space:nowrap}.fb_dialog_content .dialog_header table{-webkit-font-smoothing:subpixel-antialiased;height:43px;width:100\u0025}.fb_dialog_content .dialog_header td.header_left{font-size:12px;padding-left:5px;vertical-align:middle;width:60px}.fb_dialog_content .dialog_header td.header_right{font-size:12px;padding-right:5px;vertical-align:middle;width:60px}.fb_dialog_content .touchable_button{background:-webkit-gradient(linear, 0\u0025 0\u0025, 0\u0025 100\u0025, from(#4966A6), color-stop(.5, #355492), to(#2A4887));border:1px solid #2f477a;-webkit-background-clip:padding-box;-webkit-border-radius:3px;-webkit-box-shadow:rgba(0, 0, 0, .117188) 0 1px 1px inset, rgba(255, 255, 255, .167969) 0 1px 0;display:inline-block;margin-top:3px;max-width:85px;line-height:18px;padding:4px 12px;position:relative}.fb_dialog_content .dialog_header .touchable_button input{border:none;background:none;color:#fff;font:12px Helvetica, sans-serif;font-weight:bold;margin:2px -12px;padding:2px 6px 3px 6px;text-shadow:rgba(0, 30, 84, .296875) 0 -1px 0}.fb_dialog_content .dialog_header .header_center{color:#fff;font-size:16px;font-weight:bold;line-height:18px;text-align:center;vertical-align:middle}.fb_dialog_content .dialog_content{background:url(https:\/\/fbstatic-a.akamaihd.net\/rsrc.php\/v2\/y9\/r\/jKEcVPZFk-2.gif) no-repeat 50\u0025 50\u0025;border:1px solid #555;border-bottom:0;border-top:0;height:150px}.fb_dialog_content .dialog_footer{background:#f6f7f8;border:1px solid #555;border-top-color:#ccc;height:40px}#fb_dialog_loader_close{float:left}.fb_dialog.fb_dialog_mobile .fb_dialog_close_button{text-shadow:rgba(0, 30, 84, .296875) 0 -1px 0}.fb_dialog.fb_dialog_mobile .fb_dialog_close_icon{visibility:hidden}#fb_dialog_loader_spinner{animation:rotateSpinner 1.2s linear infinite;background-color:transparent;background-image:url(https:\/\/fbstatic-a.akamaihd.net\/rsrc.php\/v2\/yD\/r\/t-wz8gw1xG1.png);background-repeat:no-repeat;background-position:50\u0025 50\u0025;height:24px;width:24px}\u0040keyframes rotateSpinner{0\u0025{transform:rotate(0deg)}100\u0025{transform:rotate(360deg)}}\n.fb_iframe_widget{display:inline-block;position:relative}.fb_iframe_widget span{display:inline-block;position:relative;text-align:justify}.fb_iframe_widget iframe{position:absolute}.fb_iframe_widget_fluid_desktop,.fb_iframe_widget_fluid_desktop span,.fb_iframe_widget_fluid_desktop iframe{max-width:100\u0025}.fb_iframe_widget_fluid_desktop iframe{min-width:220px;position:relative}.fb_iframe_widget_lift{z-index:1}.fb_hide_iframes iframe{position:relative;left:-10000px}.fb_iframe_widget_loader{position:relative;display:inline-block}.fb_iframe_widget_fluid{display:inline}.fb_iframe_widget_fluid span{width:100\u0025}.fb_iframe_widget_loader iframe{min-height:32px;z-index:2;zoom:1}.fb_iframe_widget_loader .FB_Loader{background:url(https:\/\/fbstatic-a.akamaihd.net\/rsrc.php\/v2\/y9\/r\/jKEcVPZFk-2.gif) no-repeat;height:32px;width:32px;margin-left:-16px;position:absolute;left:50\u0025;z-index:4}","components":["css:fb.css.base","css:fb.css.dialog","css:fb.css.iframewidget"]});__d("ApiClientConfig",[],{"FlashRequest":{"swfUrl":"https:\/\/connect.facebook.net\/rsrc.php\/v1\/yd\/r\/mxzow1Sdmxr.swf"}});__d("JSSDKCanvasPrefetcherConfig",[],{"blacklist":[144959615576466],"sampleRate":500});__d("JSSDKPluginPipeConfig",[],{"threshold":0,"enabledApps":{"209753825810663":1,"187288694643718":1}}); __d("DOMWrapper",[],function a(b,c,d,e,f,g){if(c.__markCompiled)c.__markCompiled();var h,i,j={setRoot:function(k){h=k;},getRoot:function(){return h||document.body;},setWindow:function(k){i=k;},getWindow:function(){return i||self;}};f.exports=j;},null); __d('dotAccess',[],function a(b,c,d,e,f,g){if(c.__markCompiled)c.__markCompiled();function h(i,j,k){var l=j.split('.');do{var m=l.shift();i=i[m]||k&&(i[m]={});}while(l.length&&i);return i;}f.exports=h;},null); __d('guid',[],function a(b,c,d,e,f,g){if(c.__markCompiled)c.__markCompiled();function h(){return 'f'+(Math.random()*(1<<30)).toString(16).replace('.','');}f.exports=h;},null); __d('wrapFunction',[],function a(b,c,d,e,f,g){if(c.__markCompiled)c.__markCompiled();var h={};function i(j,k,l){k=k||'default';return function(){var m=k in h?h[k](j,l):j;return m.apply(this,arguments);};}i.setWrapper=function(j,k){k=k||'default';h[k]=j;};f.exports=i;},null); __d('GlobalCallback',['DOMWrapper','dotAccess','guid','wrapFunction'],function a(b,c,d,e,f,g,h,i,j,k){if(c.__markCompiled)c.__markCompiled();var l,m,n={setPrefix:function(o){l=i(h.getWindow(),o,true);m=o;},create:function(o,p){if(!l)this.setPrefix('__globalCallbacks');var q=j();l[q]=k(o,'entry',p||'GlobalCallback');return m+'.'+q;},remove:function(o){var p=o.substring(m.length+1);delete l[p];}};f.exports=n;},null); __d("sprintf",[],function a(b,c,d,e,f,g){if(c.__markCompiled)c.__markCompiled();function h(i){for(var j=arguments.length,k=Array(j>1?j-1:0),l=1;l=m)p[l in p?l:'log'](o);}var k={level:-1,Level:i,debug:ES(j,'bind',true,null,'debug',i.DEBUG),info:ES(j,'bind',true,null,'info',i.INFO),warn:ES(j,'bind',true,null,'warn',i.WARNING),error:ES(j,'bind',true,null,'error',i.ERROR)};f.exports=k;},null); __d("ObservableMixin",[],function a(b,c,d,e,f,g){if(c.__markCompiled)c.__markCompiled();function h(){this.__observableEvents={};}h.prototype={inform:function(i){var j=Array.prototype.slice.call(arguments,1),k=Array.prototype.slice.call(this.getSubscribers(i));for(var l=0;l';l.root.innerHTML='';m=true;setTimeout(function(){l.root.innerHTML=q;l.root.firstChild.src=l.url;l.onInsert&&l.onInsert(l.root.firstChild);},0);}else{var r=document.createElement('iframe');r.id=l.id;r.name=l.name;r.onload=o;r.scrolling='no';r.style.border='none';r.style.overflow='hidden';if(l.title)r.title=l.title;if(l.className)r.className=l.className;if(l.height!==undefined)r.style.height=l.height+'px';if(l.width!==undefined)if(l.width=='100%'){r.style.width=l.width;}else r.style.width=l.width+'px';l.root.appendChild(r);m=true;r.src=l.url;l.onInsert&&l.onInsert(r);}}f.exports=k;},null); __d('sdk.domReady',[],function a(b,c,d,e,f,g){if(c.__markCompiled)c.__markCompiled();var h,i="readyState" in document?/loaded|complete/.test(document.readyState):!!document.body;function j(){if(!h)return;var m;while(m=h.shift())m();h=null;}function k(m){if(h){h.push(m);}else m();}if(!i){h=[];if(document.addEventListener){document.addEventListener('DOMContentLoaded',j,false);window.addEventListener('load',j,false);}else if(document.attachEvent){document.attachEvent('onreadystatechange',j);window.attachEvent('onload',j);}if(document.documentElement.doScroll&&window==window.top){var l=function(){try{document.documentElement.doScroll('left');}catch(m){setTimeout(l,0);return;}j();};l();}}f.exports=k;},3); __d('sdk.Content',['Log','sdk.UA','sdk.domReady'],function a(b,c,d,e,f,g,h,i,j){if(c.__markCompiled)c.__markCompiled();var k,l,m={append:function(n,o){if(!o)if(!k){k=o=document.getElementById('fb-root');if(!o){h.warn('The "fb-root" div has not been created, auto-creating');k=o=document.createElement('div');o.id='fb-root';if(i.ie()||!document.body){j(function(){document.body.appendChild(o);});}else document.body.appendChild(o);}o.className+=' fb_reset';}else o=k;if(typeof n=='string'){var p=document.createElement('div');o.appendChild(p).innerHTML=n;return p;}else return o.appendChild(n);},appendHidden:function(n){if(!o){var o=document.createElement('div'),p=o.style;p.position='absolute';p.top='-10000px';p.width=p.height=0;o=m.append(o);}return m.append(n,o);},submitToTarget:function(n,o){var p=document.createElement('form');p.action=n.url;p.target=n.target;p.method=o?'GET':'POST';m.appendHidden(p);for(var q in n.params)if(n.params.hasOwnProperty(q)){var r=n.params[q];if(r!==null&&r!==undefined){var s=document.createElement('input');s.name=q;s.value=r;p.appendChild(s);}}p.submit();p.parentNode.removeChild(p);}};f.exports=m;},null); __d('sdk.Impressions',['sdk.Content','Miny','QueryString','sdk.Runtime','UrlMap','getBlankIframeSrc','guid','insertIframe'],function a(b,c,d,e,f,g,h,i,j,k,l,m,n,o){if(c.__markCompiled)c.__markCompiled();function p(r){var s=k.getClientID();if(!r.api_key&&s)r.api_key=s;r.kid_directed_site=k.getKidDirectedSite();var t=l.resolve('www',true)+'/impression.php/'+n()+'/',u=j.appendToUrl(t,r);if(u.length>2000)if(r.payload&&typeof r.payload==='string'){var v=i.encode(r.payload);if(v&&v.length>>18),h.charCodeAt(m>>>12&63),h.charCodeAt(m>>>6&63),h.charCodeAt(m&63));}var j='>___?456789:;<=_______'+'\x00\x01\x02\x03\x04\x05\x06\x07\b\t\n\x0b\f\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19'+'______\x1a\x1b\x1c\x1d\x1e\x1f !"#$%&\'()*+,-./0123';function k(m){m=j.charCodeAt(m.charCodeAt(0)-43)<<18|j.charCodeAt(m.charCodeAt(1)-43)<<12|j.charCodeAt(m.charCodeAt(2)-43)<<6|j.charCodeAt(m.charCodeAt(3)-43);return String.fromCharCode(m>>>16,m>>>8&255,m&255);}var l={encode:function(m){m=unescape(encodeURI(m));var n=(m.length+2)%3;m=(m+'\0\0'.slice(n)).replace(/[\s\S]{3}/g,i);return m.slice(0,m.length+n-2)+'=='.slice(n);},decode:function(m){m=m.replace(/[^A-Za-z0-9+\/]/g,'');var n=m.length+3&3;m=(m+'AAA'.slice(n)).replace(/..../g,k);m=m.slice(0,m.length+n-3);try{return decodeURIComponent(escape(m));}catch(o){throw new Error('Not valid UTF-8');}},encodeObject:function(m){return l.encode(ES('JSON','stringify',false,m));},decodeObject:function(m){return ES('JSON','parse',false,l.decode(m));},encodeNums:function(m){return String.fromCharCode.apply(String,ES(m,'map',true,function(n){return h.charCodeAt((n|-(n>63))&-(n>0)&63);}));}};f.exports=l;},null); __d('sdk.SignedRequest',['Base64'],function a(b,c,d,e,f,g,h){if(c.__markCompiled)c.__markCompiled();function i(k){if(!k)return null;var l=k.split('.',2)[1].replace(/\-/g,'+').replace(/\_/g,'/');return h.decodeObject(l);}var j={parse:i};f.exports=j;},null); __d('URIRFC3986',[],function a(b,c,d,e,f,g){if(c.__markCompiled)c.__markCompiled();var h=new RegExp('^'+'([^:/?#]+:)?'+'(//'+'([^\\\\/?#@]*@)?'+'('+'\\[[A-Fa-f0-9:.]+\\]|'+'[^\\/?#:]*'+')'+'(:[0-9]*)?'+')?'+'([^?#]*)'+'(\\?[^#]*)?'+'(#.*)?'),i={parse:function(j){if(ES(j,'trim',true)==='')return null;var k=j.match(h),l={};l.uri=k[0]?k[0]:null;l.scheme=k[1]?k[1].substr(0,k[1].length-1):null;l.authority=k[2]?k[2].substr(2):null;l.userinfo=k[3]?k[3].substr(0,k[3].length-1):null;l.host=k[2]?k[4]:null;l.port=k[5]?k[5].substr(1)?parseInt(k[5].substr(1),10):null:null;l.path=k[6]?k[6]:null;l.query=k[7]?k[7].substr(1):null;l.fragment=k[8]?k[8].substr(1):null;l.isGenericURI=l.authority===null&&!!l.scheme;return l;}};f.exports=i;},null); __d('createObjectFrom',[],function a(b,c,d,e,f,g){if(c.__markCompiled)c.__markCompiled();function h(i,j){var k={},l=ES('Array','isArray',false,j);if(j===undefined)j=true;for(var m=i.length-1;m>=0;m--)k[i[m]]=l?j[m]:j;return k;}f.exports=h;},null); __d('URISchemes',['createObjectFrom'],function a(b,c,d,e,f,g,h){if(c.__markCompiled)c.__markCompiled();var i=h(['blob','fb','fb-ama','fb-messenger','fbcf','fbconnect','fbmobilehome','fbrpc','file','ftp','http','https','mailto','ms-app','intent','itms','itms-apps','itms-services','market','svn+ssh','fbstaging','tel','sms','pebblejs','sftp']),j={isAllowed:function(k){if(!k)return true;return i.hasOwnProperty(k.toLowerCase());}};f.exports=j;},null); __d('eprintf',[],function a(b,c,d,e,f,g){if(c.__markCompiled)c.__markCompiled();var h=function(i){var j=ES(Array.prototype.slice.call(arguments),'map',true,function(m){return String(m);}),k=i.split('%s').length-1;if(k!==j.length-1)return h('eprintf args number mismatch: %s',ES('JSON','stringify',false,j));var l=1;return i.replace(/%s/g,function(m){return String(j[l++]);});};f.exports=h;},null); __d('ex',['eprintf'],function a(b,c,d,e,f,g,h){if(c.__markCompiled)c.__markCompiled();var i=function(){for(var j=arguments.length,k=Array(j),l=0;l0||this.getFragment());};p.prototype.toString=function(){'use strict';var q=this;for(var r=0;ri;},ie64:function(){return y.ie()&&s;},firefox:function(){return x()||j;},opera:function(){return x()||k;},webkit:function(){return x()||l;},safari:function(){return y.webkit();},chrome:function(){return x()||m;},windows:function(){return x()||p;},osx:function(){return x()||o;},linux:function(){return x()||q;},iphone:function(){return x()||t;},mobile:function(){return x()||(t||u||r||w);},nativeApp:function(){return x()||v;},android:function(){return x()||r;},ipad:function(){return x()||u;}};f.exports=y;},null); __d('htmlSpecialChars',[],function a(b,c,d,e,f,g){if(c.__markCompiled)c.__markCompiled();var h=/&/g,i=//g,k=/"/g,l=/'/g;function m(n){if(typeof n=='undefined'||n===null||!n.toString)return '';if(n===false){return '0';}else if(n===true)return '1';return n.toString().replace(h,'&').replace(k,'"').replace(l,''').replace(i,'<').replace(j,'>');}f.exports=m;},null); __d('Flash',['DOMEventListener','DOMWrapper','QueryString','UserAgent_DEPRECATED','guid','htmlSpecialChars'],function a(b,c,d,e,f,g,h,i,j,k,l,m){if(c.__markCompiled)c.__markCompiled();var n={},o,p=i.getWindow().document;function q(v){var w=p.getElementById(v);if(w)w.parentNode.removeChild(w);delete n[v];}function r(){for(var v in n)if(n.hasOwnProperty(v))q(v);}function s(v){return v.replace(/\d+/g,function(w){return '000'.substring(w.length)+w;});}function t(v){if(!o){if(k.ie()>=9)h.add(window,'unload',r);o=true;}n[v]=v;}var u={embed:function(v,w,x,y){var z=l();v=m(v).replace(/&/g,'&');x=babelHelpers._extends({allowscriptaccess:'always',flashvars:y,movie:v},x);if(typeof x.flashvars=='object')x.flashvars=j.encode(x.flashvars);var aa=[];for(var ba in x)if(x.hasOwnProperty(ba)&&x[ba])aa.push('');var ca=w.appendChild(p.createElement('span')),da=''+aa.join('')+'';ca.innerHTML=da;var ea=ca.firstChild;t(z);return ea;},remove:q,getVersion:function(){var v='Shockwave Flash',w='application/x-shockwave-flash',x='ShockwaveFlash.ShockwaveFlash',y;if(navigator.plugins&&typeof navigator.plugins[v]=='object'){var z=navigator.plugins[v].description;if(z&&navigator.mimeTypes&&navigator.mimeTypes[w]&&navigator.mimeTypes[w].enabledPlugin)y=z.match(/\d+/g);}if(!y)try{y=new ActiveXObject(x).GetVariable('$version').match(/(\d+),(\d+),(\d+),(\d+)/);y=Array.prototype.slice.call(y,1);}catch(aa){}return y;},getVersionString:function(){var v=u.getVersion();return v?v.join('.'):'';},checkMinVersion:function(v){var w=u.getVersion();if(!w)return false;return s(w.join('.'))>=s(v);},isAvailable:function(){return !!u.getVersion();}};f.exports=u;},null); __d("emptyFunction",[],function a(b,c,d,e,f,g){if(c.__markCompiled)c.__markCompiled();function h(j){return function(){return j;};}function i(){}i.thatReturns=h;i.thatReturnsFalse=h(false);i.thatReturnsTrue=h(true);i.thatReturnsNull=h(null);i.thatReturnsThis=function(){return this;};i.thatReturnsArgument=function(j){return j;};f.exports=i;},null); __d('XDM',['DOMEventListener','DOMWrapper','emptyFunction','Flash','GlobalCallback','guid','Log','UserAgent_DEPRECATED','wrapFunction'],function a(b,c,d,e,f,g,h,i,j,k,l,m,n,o,p){if(c.__markCompiled)c.__markCompiled();var q={},r={transports:[]},s=i.getWindow();function t(w){var x={},y=w.length,z=r.transports;while(y--)x[w[y]]=1;y=z.length;while(y--){var aa=z[y],ba=q[aa];if(!x[aa]&&ba.isAvailable())return aa;}}var u={register:function(w,x){n.debug('Registering %s as XDM provider',w);r.transports.push(w);q[w]=x;},create:function(w){if(!w.whenReady&&!w.onMessage){n.error('An instance without whenReady or onMessage makes no sense');throw new Error('An instance without whenReady or '+'onMessage makes no sense');}if(!w.channel){n.warn('Missing channel name, selecting at random');w.channel=m();}if(!w.whenReady)w.whenReady=j;if(!w.onMessage)w.onMessage=j;var x=w.transport||t(w.blacklist||[]),y=q[x];if(y&&y.isAvailable()){n.debug('%s is available',x);y.init(w);return x;}}};u.register('flash',(function(){var w=false,x,y=false,z=15000,aa;return {isAvailable:function(){return k.checkMinVersion('8.0.24');},init:function(ba){n.debug('init flash: '+ba.channel);var ca={send:function(fa,ga,ha,ia){n.debug('sending to: %s (%s)',ga,ia);x.postMessage(fa,ga,ia);}};if(w){ba.whenReady(ca);return;}var da=ba.root.appendChild(s.document.createElement('div')),ea=l.create(function(){l.remove(ea);clearTimeout(aa);n.info('xdm.swf called the callback');var fa=l.create(function(ga,ha){ga=decodeURIComponent(ga);ha=decodeURIComponent(ha);n.debug('received message %s from %s',ga,ha);ba.onMessage(ga,ha);},'xdm.swf:onMessage');x.init(ba.channel,fa);ba.whenReady(ca);},'xdm.swf:load');x=k.embed(ba.flashUrl,da,null,{protocol:location.protocol.replace(':',''),host:location.host,callback:ea,log:y});aa=setTimeout(function(){n.warn('The Flash component did not load within %s ms - '+'verify that the container is not set to hidden or invisible '+'using CSS as this will cause some browsers to not load '+'the components',z);},z);w=true;}};})());var v=/\.facebook\.com(\/|$)/;u.register('postmessage',(function(){var w=false;return {isAvailable:function(){return !!s.postMessage;},init:function(x){n.debug('init postMessage: '+x.channel);var y='_FB_'+x.channel,z={send:function(aa,ba,ca,da){if(s===ca){n.error('Invalid windowref, equal to window (self)');throw new Error();}n.debug('sending to: %s (%s)',ba,da);var ea=function(){ca.postMessage('_FB_'+da+aa,ba);};if(o.ie()==8||o.ieCompatibilityMode()){setTimeout(ea,0);}else ea();}};if(w){x.whenReady(z);return;}h.add(s,'message',p(function(event){var aa=event.data,ba=event.origin||'native';if(!/^(https?:\/\/|native$)/.test(ba)){n.debug('Received message from invalid origin type: %s',ba);return;}if(ba!=='native'&&!(v.test(location.hostname)||v.test(event.origin)))return;if(typeof aa!='string'){n.warn('Received message of type %s from %s, expected a string',typeof aa,ba);return;}n.debug('received message %s from %s',aa,ba);if(aa.substring(0,y.length)==y)aa=aa.substring(y.length);x.onMessage(aa,ba);},'entry','onMessage'));x.whenReady(z);w=true;}};})());f.exports=u;},null); __d('isFacebookURI',[],function a(b,c,d,e,f,g){if(c.__markCompiled)c.__markCompiled();var h=null,i=['http','https'];function j(k){if(!h)h=new RegExp('(^|\\.)facebook\\.com$','i');if(k.isEmpty()&&k.toString()!=='#')return false;if(!k.getDomain()&&!k.getProtocol())return true;return ES(i,'indexOf',true,k.getProtocol())!==-1&&h.test(k.getDomain());}j.setRegex=function(k){h=k;};f.exports=j;},null); __d('sdk.Event',[],function a(b,c,d,e,f,g){if(c.__markCompiled)c.__markCompiled();var h={SUBSCRIBE:'event.subscribe',UNSUBSCRIBE:'event.unsubscribe',subscribers:function(){if(!this._subscribersMap)this._subscribersMap={};return this._subscribersMap;},subscribe:function(i,j){var k=this.subscribers();if(!k[i]){k[i]=[j];}else if(ES(k[i],'indexOf',true,j)==-1)k[i].push(j);if(i!=this.SUBSCRIBE&&i!=this.UNSUBSCRIBE)this.fire(this.SUBSCRIBE,i,k[i]);},unsubscribe:function(i,j){var k=this.subscribers()[i];if(k)ES(k,'forEach',true,function(l,m){if(l==j)k.splice(m,1);});if(i!=this.SUBSCRIBE&&i!=this.UNSUBSCRIBE)this.fire(this.UNSUBSCRIBE,i,k);},monitor:function(i,j){if(!j()){var k=this,l=function(){if(j.apply(j,arguments))k.unsubscribe(i,l);};this.subscribe(i,l);}},clear:function(i){delete this.subscribers()[i];},fire:function(i){var j=Array.prototype.slice.call(arguments,1),k=this.subscribers()[i];if(k)ES(k,'forEach',true,function(l){if(l)l.apply(this,j);});}};f.exports=h;},null); __d('JSONRPC',['Log'],function a(b,c,d,e,f,g,h){if(c.__markCompiled)c.__markCompiled();function i(j){'use strict';this.$JSONRPC1=0;this.$JSONRPC2={};this.remote=ES(function(k){this.$JSONRPC3=k;return this.remote;},'bind',true,this);this.local={};this.$JSONRPC4=j;}i.prototype.stub=function(j){'use strict';this.remote[j]=ES(function(){var k={jsonrpc:'2.0',method:j};for(var l=arguments.length,m=Array(l),n=0;n');}else{n=document.createElement("iframe");n.name=o;}delete m.style;delete m.name;delete m.url;delete m.root;delete m.onload;delete m.onerror;var u=ES('Object','assign',false,{frameBorder:0,allowTransparency:true,allowFullscreen:true,scrolling:'no'},m);if(u.width)n.width=u.width+'px';if(u.height)n.height=u.height+'px';delete u.height;delete u.width;for(var v in u)if(u.hasOwnProperty(v))n.setAttribute(v,u[v]);ES('Object','assign',false,n.style,q);n.src=i();p.appendChild(n);if(s)var w=h.add(n,'load',function(){w.remove();s();});if(t)var x=h.add(n,'error',function(){x.remove();t();});n.src=r;return n;}f.exports=l;},null); __d('sdk.feature',['JSSDKConfig','invariant'],function a(b,c,d,e,f,g,h,i){if(c.__markCompiled)c.__markCompiled();function j(k,l){!(arguments.length>=2)?i(0):undefined;if(h.features&&k in h.features){var m=h.features[k];if(typeof m==='object'&&typeof m.rate==='number'){if(m.rate&&Math.random()*100<=m.rate){return m.value||true;}else return m.value?null:false;}else return m;}return l;}f.exports=j;},null); __d('sdk.XD',['sdk.Content','sdk.Event','Log','QueryString','Queue','sdk.RPC','sdk.Runtime','sdk.Scribe','sdk.URI','UrlMap','JSSDKXDConfig','XDM','isFacebookURI','sdk.createIframe','sdk.feature','guid'],function a(b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w){if(c.__markCompiled)c.__markCompiled();var x=new l(),y=new l(),z=new l(),aa,ba,ca=w(),da=r.useCdn?'cdn':'www',ea=v('use_bundle',false)?r.XdBundleUrl:r.XdUrl,fa=q.resolve(da,false)+ea,ga=q.resolve(da,true)+ea,ha=w(),ia=location.protocol+'//'+location.host,ja,ka=false,la='Facebook Cross Domain Communication Frame',ma={},na=new l();m.setInQueue(na);function oa(ua){j.info('Remote XD can talk to facebook.com (%s)',ua);n.setEnvironment(ua==='canvas'?n.ENVIRONMENTS.CANVAS:n.ENVIRONMENTS.PAGETAB);}function pa(ua,va){if(!va){j.error('No senderOrigin');throw new Error();}var wa=/^https?/.exec(va)[0];switch(ua.xd_action){case 'proxy_ready':var xa,ya;if(wa=='https'){xa=z;ya=ba;n.setLoggedIntoFacebook(ua.logged_in==='true');}else{xa=y;ya=aa;}if(ua.registered){oa(ua.registered);x=xa.merge(x);}j.info('Proxy ready, starting queue %s containing %s messages',wa+'ProxyQueue',xa.getLength());xa.start(function(ab){ja.send(typeof ab==='string'?ab:k.encode(ab),va,ya.contentWindow,ha+'_'+wa);});break;case 'plugin_ready':j.info('Plugin %s ready, protocol: %s',ua.name,wa);ma[ua.name]={protocol:wa};if(l.exists(ua.name)){var za=l.get(ua.name);j.debug('Enqueuing %s messages for %s in %s',za.getLength(),ua.name,wa+'ProxyQueue');(wa=='https'?z:y).merge(za);}break;}if(ua.data)qa(ua.data,va);}function qa(ua,va){if(va&&va!=='native'&&!t(new p(va)))return;if(typeof ua=='string'){if(/^FB_RPC:/.test(ua)){na.enqueue(ua.substring(7));return;}if(ua.substring(0,1)=='{'){try{ua=ES('JSON','parse',false,ua);}catch(wa){j.warn('Failed to decode %s as JSON',ua);return;}}else ua=k.decode(ua);}if(!va)if(ua.xd_sig==ca)va=ua.xd_origin;if(ua.xd_action){pa(ua,va);return;}if(ua.access_token)n.setSecure(/^https/.test(ia));if(ua.cb){var xa=ta._callbacks[ua.cb];if(!ta._forever[ua.cb])delete ta._callbacks[ua.cb];if(xa)xa(ua);}}function ra(ua,va){if(ua=='facebook'){va.relation='parent.parent';x.enqueue(va);}else{va.relation='parent.frames["'+ua+'"]';var wa=ma[ua];if(wa){j.debug('Enqueuing message for plugin %s in %s',ua,wa.protocol+'ProxyQueue');(wa.protocol=='https'?z:y).enqueue(va);}else{j.debug('Buffering message for plugin %s',ua);l.get(ua).enqueue(va);}}}m.getOutQueue().start(function(ua){ra('facebook','FB_RPC:'+ua);});function sa(ua){if(ka)return;var va=h.appendHidden(document.createElement('div')),wa=s.create({blacklist:null,root:va,channel:ha,flashUrl:r.Flash.path,whenReady:function(xa){ja=xa;var ya={channel:ha,origin:location.protocol+'//'+location.host,transport:wa,xd_name:ua},za='#'+k.encode(ya);if(n.getSecure()!==true)aa=u({url:fa+za,name:'fb_xdm_frame_http',id:'fb_xdm_frame_http',root:va,'aria-hidden':true,title:la,tabindex:-1});ba=u({url:ga+za,name:'fb_xdm_frame_https',id:'fb_xdm_frame_https',root:va,'aria-hidden':true,title:la,tabindex:-1});},onMessage:qa});if(!wa)o.log('jssdk_error',{appId:n.getClientID(),error:'XD_TRANSPORT',extra:{message:'Failed to create a valid transport'}});ka=true;}var ta={rpc:m,_callbacks:{},_forever:{},_channel:ha,_origin:ia,onMessage:qa,recv:qa,init:sa,sendToFacebook:ra,inform:function(ua,va,wa,xa){ra('facebook',{method:ua,params:ES('JSON','stringify',false,va||{}),behavior:xa||'p',relation:wa});},handler:function(ua,va,wa,xa){var ya='#'+k.encode({cb:this.registerCallback(ua,wa,xa),origin:ia+'/'+ha,domain:location.hostname,relation:va||'opener'});return (location.protocol=='https:'?ga:fa)+ya;},registerCallback:function(ua,va,wa){wa=wa||w();if(va)ta._forever[wa]=true;ta._callbacks[wa]=ua;return wa;}};i.subscribe('init:post',function(ua){sa(ua.xdProxyName);var va=v('xd_timeout',false);if(va)setTimeout(function(){var wa=ba&&(!!aa==y.isStarted()&&!!ba==z.isStarted());if(!wa)o.log('jssdk_error',{appId:n.getClientID(),error:'XD_INITIALIZATION',extra:{message:'Failed to initialize in '+va+'ms'}});},va);});f.exports=ta;},null); __d('sdk.getContextType',['sdk.Runtime','sdk.UA'],function a(b,c,d,e,f,g,h,i){if(c.__markCompiled)c.__markCompiled();function j(){if(i.nativeApp())return 3;if(i.mobile())return 2;if(h.isEnvironment(h.ENVIRONMENTS.CANVAS))return 5;return 1;}f.exports=j;},null); __d('sdk.Auth',['sdk.Cookie','sdk.createIframe','DOMWrapper','sdk.feature','sdk.getContextType','guid','sdk.Impressions','Log','ObservableMixin','sdk.Runtime','sdk.SignedRequest','UrlMap','sdk.URI','sdk.XD'],function a(b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u){if(c.__markCompiled)c.__markCompiled();var v='fblo_',w=365*24*60*60*1000,x,y,z=new p();function aa(ga,ha){var ia=q.getUserID(),ja='';if(ga)if(ga.userID){ja=ga.userID;}else if(ga.signedRequest){var ka=r.parse(ga.signedRequest);if(ka&&ka.user_id)ja=ka.user_id;}var la=q.getLoginStatus(),ma=la==='unknown'&&ga||q.getUseCookie()&&q.getCookieUserID()!==ja,na=ia&&!ga,oa=ga&&ia&&ia!=ja,pa=ga!=x,qa=ha!=(la||'unknown');q.setLoginStatus(ha);q.setAccessToken(ga&&ga.accessToken||null);q.setUserID(ja);x=ga;var ra={authResponse:ga,status:ha};if(na||oa)z.inform('logout',ra);if(ma||oa)z.inform('login',ra);if(pa)z.inform('authresponse.change',ra);if(qa)z.inform('status.change',ra);return ra;}function ba(){return x;}function ca(ga,ha,ia){return function(ja){var ka;if(ja&&ja.access_token){var la=r.parse(ja.signed_request);ha={accessToken:ja.access_token,userID:la.user_id,expiresIn:parseInt(ja.expires_in,10),signedRequest:ja.signed_request};if(ja.granted_scopes)ha.grantedScopes=ja.granted_scopes;if(q.getUseCookie()){var ma=ha.expiresIn===0?0:ES('Date','now',false)+ha.expiresIn*1000,na=h.getDomain();if(!na&&ja.base_domain)h.setDomain('.'+ja.base_domain);h.setSignedRequestCookie(ja.signed_request,ma);h.setRaw(v,'',0);}ka='connected';aa(ha,ka);}else if(ia==='logout'||ia==='login_status'){if(ja.error&&ja.error==='not_authorized'){ka='not_authorized';}else ka='unknown';aa(null,ka);if(q.getUseCookie())h.clearSignedRequestCookie();if(ia==='logout')h.setRaw(v,'y',ES('Date','now',false)+w);}if(ja&&ja.https==1)q.setSecure(true);if(ga)ga({authResponse:ha,status:q.getLoginStatus()});return ha;};}function da(ga){var ha,ia=ES('Date','now',false);if(y){clearTimeout(y);y=null;}if(h.getRaw(v)==='y'){var ja='unknown';aa(null,ja);if(ga)ga({authResponse:null,status:ja});return;}var ka=ca(ga,x,'login_status'),la=new t(s.resolve('www',true)+'/connect/ping').setQueryData({client_id:q.getClientID(),response_type:'token,signed_request,code',domain:location.hostname,origin:l(),redirect_uri:u.handler(function(ma){if(k('e2e_ping_tracking',true)){var na={init:ia,close:ES('Date','now',false),method:'ping'};o.debug('e2e: %s',ES('JSON','stringify',false,na));n.log(114,{payload:na});}ha.parentNode.removeChild(ha);if(ka(ma))y=setTimeout(function(){da(function(){});},1200000);},'parent'),sdk:'joey',kid_directed_site:q.getKidDirectedSite()});ha=i({root:j.getRoot(),name:m(),url:la.toString(),style:{display:'none'}});}var ea;function fa(ga,ha){if(!q.getClientID()){o.warn('FB.getLoginStatus() called before calling FB.init().');return;}if(ga)if(!ha&&ea=='loaded'){ga({status:q.getLoginStatus(),authResponse:ba()});return;}else z.subscribe('FB.loginStatus',ga);if(!ha&&ea=='loading')return;ea='loading';var ia=function(ja){ea='loaded';z.inform('FB.loginStatus',ja);z.clearSubscribers('FB.loginStatus');};da(ia);}ES('Object','assign',false,z,{getLoginStatus:fa,fetchLoginStatus:da,setAuthResponse:aa,getAuthResponse:ba,parseSignedRequest:r.parse,xdResponseWrapper:ca});f.exports=z;},null); __d('sdk.DOM',['Assert','sdk.UA','sdk.domReady'],function a(b,c,d,e,f,g,h,i,j){if(c.__markCompiled)c.__markCompiled();var k={};function l(z,aa){var ba=z.getAttribute(aa)||z.getAttribute(aa.replace(/_/g,'-'))||z.getAttribute(aa.replace(/-/g,'_'))||z.getAttribute(aa.replace(/-/g,''))||z.getAttribute(aa.replace(/_/g,''))||z.getAttribute('data-'+aa)||z.getAttribute('data-'+aa.replace(/_/g,'-'))||z.getAttribute('data-'+aa.replace(/-/g,'_'))||z.getAttribute('data-'+aa.replace(/-/g,''))||z.getAttribute('data-'+aa.replace(/_/g,''));return ba?String(ba):null;}function m(z,aa){var ba=l(z,aa);return ba?/^(true|1|yes|on)$/.test(ba):null;}function n(z,aa){h.isTruthy(z,'element not specified');h.isString(aa);try{return String(z[aa]);}catch(ba){throw new Error('Could not read property '+aa+' : '+ba.message);}}function o(z,aa){h.isTruthy(z,'element not specified');h.isString(aa);try{z.innerHTML=aa;}catch(ba){throw new Error('Could not set innerHTML : '+ba.message);}}function p(z,aa){h.isTruthy(z,'element not specified');h.isString(aa);var ba=' '+n(z,'className')+' ';return ES(ba,'indexOf',true,' '+aa+' ')>=0;}function q(z,aa){h.isTruthy(z,'element not specified');h.isString(aa);if(!p(z,aa))z.className=n(z,'className')+' '+aa;}function r(z,aa){h.isTruthy(z,'element not specified');h.isString(aa);var ba=new RegExp('\\s*'+aa,'g');z.className=ES(n(z,'className').replace(ba,''),'trim',true);}function s(z,aa,ba){h.isString(z);aa=aa||document.body;ba=ba||'*';if(aa.querySelectorAll)return ES('Array','from',false,aa.querySelectorAll(ba+'.'+z));var ca=aa.getElementsByTagName(ba),da=[];for(var ea=0,fa=ca.length;eak){i.remove(p.callback);return false;}r.onerror=function(){s({error:{type:'http',message:'unknown error'}});};var t=function(){setTimeout(function(){s({error:{type:'http',message:'unknown error'}});},0);};if(r.addEventListener){r.addEventListener('load',t,false);}else r.onreadystatechange=function(){if(/loaded|complete/.test(this.readyState))t();};r.src=n;h.getRoot().appendChild(r);return true;}var m={execute:l,MAX_QUERYSTRING_LENGTH:k};f.exports=m;},null); __d('flattenObject',[],function a(b,c,d,e,f,g){if(c.__markCompiled)c.__markCompiled();function h(i){var j={};for(var k in i)if(i.hasOwnProperty(k)){var l=i[k];if(null===l||undefined===l){}else if(typeof l=='string'){j[k]=l;}else j[k]=ES('JSON','stringify',false,l);}return j;}f.exports=h;},null); __d('ApiClient',['ArgumentError','Assert','CORSRequest','FlashRequest','flattenObject','JSONPRequest','Log','ObservableMixin','QueryString','sprintf','sdk.URI','UrlMap','ApiClientConfig','invariant'],function a(b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u){if(c.__markCompiled)c.__markCompiled();var v,w,x,y=m.MAX_QUERYSTRING_LENGTH,z={get:true,post:true,'delete':true,put:true},aa={fql_query:true,fql_multiquery:true,friends_get:true,notifications_get:true,stream_get:true,users_getinfo:true},ba=['jsonp','cors','flash'],ca=[],da=[],ea=null,fa=0,ga=[],ha=0,ia=50,ja=105440539523;function ka(ua,va,wa,xa){var ya=ha!==0&&fa>=ha;if(ya){ga.push(function(){return ka(ua,va,wa,xa);});sa.inform('request.queued',ua,va,wa);return;}fa++;if(x)wa=ES('Object','assign',false,{},x,wa);wa.access_token=wa.access_token||v;wa.pretty=wa.pretty||0;wa=l(wa);var za={jsonp:m,cors:j,flash:k},ab;if(wa.transport){ab=[wa.transport];delete wa.transport;}else ab=ba;for(var bb=0;bb0&&fa0)?u(0):undefined;!(ca.length===da.length)?u(0):undefined;var ua=ca,va=da;ca=[];da=[];ea=null;if(ua.length===1){var wa=ua[0],xa=va[0],ya=wa.body?p.decode(wa.body):null;na(wa.relative_url,wa.method,ya,xa);return;}na('/','POST',{batch:ua,include_headers:false,batch_app_id:w||ja},function(za){if(ES('Array','isArray',false,za)){ES(za,'forEach',true,function(ab,bb){va[bb](ES('JSON','parse',false,ab.body));});}else ES(va,'forEach',true,function(ab){return (ab({error:{message:'Fatal: batch call failed.'}}));});});}function ra(ua,va){i.isObject(ua);i.isString(ua.method,'method missing');if(!va)n.warn('No callback passed to the ApiClient');var wa=ua.method.toLowerCase().replace('.','_');ua.format='json-strings';ua.api_key=w;var xa=wa in aa?'api_read':'api',ya=s.resolve(xa)+'/restserver.php',za=ES(la,'bind',true,null,va,'/restserver.php','get',ua,ES('Date','now',false));ka(ya,'get',ua,za);}var sa=ES('Object','assign',false,new o(),{setAccessToken:function(ua){v=ua;},setAccessTokenForClientID:function(ua,va){if(!(v&&w&&w!==va))v=ua;},getAccessToken:function(){return v;},setClientID:function(ua){w=ua;},setDefaultParams:function(ua){x=ua;},setDefaultTransports:function(ua){ba=ua;},setMaxConcurrentRequests:function(ua){ha=ua;},getCurrentlyExecutingRequestCount:function(){return fa;},getQueuedRequestCount:function(){return ga.length;},rest:ra,graph:na,scheduleBatchCall:pa,prepareBatchParams:oa});function ta(ua,va){return ua.toString().length>y&&va==='get';}k.setSwfUrl(t.FlashRequest.swfUrl);f.exports=sa;},null); __d('sdk.PlatformVersioning',['sdk.Runtime','ManagedError'],function a(b,c,d,e,f,g,h,i){if(c.__markCompiled)c.__markCompiled();var j=/^v\d+\.\d\d?$/,k={REGEX:j,assertVersionIsSet:function(){if(!h.getVersion())throw new i('init not called with valid version');},assertValidVersion:function(l){if(!j.test(l))throw new i('invalid version specified');}};f.exports=k;},null); __d('sdk.api',['ApiClient','sdk.PlatformVersioning','sdk.Runtime','sdk.Scribe','sdk.URI','sdk.feature'],function a(b,c,d,e,f,g,h,i,j,k,l,m){if(c.__markCompiled)c.__markCompiled();var n=m('should_log_response_error',false),o;j.subscribe('ClientID.change',function(q){return h.setClientID(q);});j.subscribe('AccessToken.change',function(q){o=q;h.setAccessToken(q);});h.setDefaultParams({sdk:'joey'});h.subscribe('request.complete',function(q,r,s,t){var u=false;if(t&&typeof t=='object')if(t.error){if(t.error=='invalid_token'||t.error.type=='OAuthException'&&t.error.code==190)u=true;}else if(t.error_code)if(t.error_code=='190')u=true;if(u&&o===j.getAccessToken())j.setAccessToken(null);});h.subscribe('request.complete',function(q,r,s,t){if((q=='/me/permissions'&&r==='delete'||q=='/restserver.php'&&s.method=='Auth.revokeAuthorization')&&t===true)j.setAccessToken(null);});h.subscribe('request.error',function(q,r,s,t){if(n&&t.error.type==='http')k.log('jssdk_error',{appId:j.getClientID(),error:'transport',extra:{name:'transport',message:ES('JSON','stringify',false,t.error)}});});function p(q){if(typeof q==='string'){if(j.getIsVersioned()){i.assertVersionIsSet();if(!/https?/.test(q)&&q.charAt(0)!=='/')q='/'+q;q=new l(q).setDomain(null).setProtocol(null).toString();if(!i.REGEX.test(q.substring(1,ES(q,'indexOf',true,'/',1))))q='/'+j.getVersion()+q;var r=[q].concat(Array.prototype.slice.call(arguments,1));h.graph.apply(h,r);}else h.graph.apply(h,arguments);}else h.rest.apply(h,arguments);}f.exports=p;},null); __d('legacy:fb.api',['FB','sdk.api'],function a(b,c,d,e,f,g,h,i){if(c.__markCompiled)c.__markCompiled();h.provide('',{api:i});},3); __d('sdk.AppEvents',['Assert','sdk.Impressions','sdk.Runtime'],function a(b,c,d,e,f,g,h,i,j){if(c.__markCompiled)c.__markCompiled();var k={COMPLETED_REGISTRATION:'fb_mobile_complete_registration',VIEWED_CONTENT:'fb_mobile_content_view',SEARCHED:'fb_mobile_search',RATED:'fb_mobile_rate',COMPLETED_TUTORIAL:'fb_mobile_tutorial_completion',ADDED_TO_CART:'fb_mobile_add_to_cart',ADDED_TO_WISHLIST:'fb_mobile_add_to_wishlist',INITIATED_CHECKOUT:'fb_mobile_initiated_checkout',ADDED_PAYMENT_INFO:'fb_mobile_add_payment_info',ACHIEVED_LEVEL:'fb_mobile_level_achieved',UNLOCKED_ACHIEVEMENT:'fb_mobile_achievement_unlocked',SPENT_CREDITS:'fb_mobile_spent_credits'},l={ACTIVATED_APP:'fb_mobile_activate_app',PURCHASED:'fb_mobile_purchase'},m={CURRENCY:'fb_currency',REGISTRATION_METHOD:'fb_registration_method',CONTENT_TYPE:'fb_content_type',CONTENT_ID:'fb_content_id',SEARCH_STRING:'fb_search_string',SUCCESS:'fb_success',MAX_RATING_VALUE:'fb_max_rating_value',PAYMENT_INFO_AVAILABLE:'fb_payment_info_available',NUM_ITEMS:'fb_num_items',LEVEL:'fb_level',DESCRIPTION:'fb_description'},n=40,o='^[0-9a-zA-Z_]+[0-9a-zA-Z _-]*$';function p(t,u,v,w){h.isTrue(q(u),'Invalid event name: '+u+'. '+'It must be between 1 and '+n+' characters, '+'and must be contain only alphanumerics, _, - or spaces, '+'starting with alphanumeric or _.');var x={ae:1,ev:u,vts:v,canvas:j.isCanvasEnvironment()?1:0};if(w)x.cd=w;i.impression({api_key:t,payload:ES('JSON','stringify',false,x)});}function q(t){if(t===null||t.length===0||t.length>n||!new RegExp(o).test(t))return false;return true;}function r(t,u,v,w){var x={};x[m.CURRENCY]=v;p(t,l.PURCHASED,u,babelHelpers._extends({},w,x));}function s(t){p(t,l.ACTIVATED_APP);}f.exports={activateApp:s,logEvent:p,logPurchase:r,isValidEventName:q,EventNames:k,ParameterNames:m};},null); __d('legacy:fb.appevents',['Assert','sdk.AppEvents','FB','sdk.feature','sdk.Runtime'],function a(b,c,d,e,f,g,h,i,j,k,l){if(c.__markCompiled)c.__markCompiled();j.provide('AppEvents',{logEvent:function(m,n,o){h.isTrue(k('allow_non_canvas_app_events',false)||l.isCanvasEnvironment(),'You can only use this function in Facebook Canvas environment');h.isString(m,'Invalid eventName');h.maybeNumber(n,'Invalid valueToSum');h.maybeObject(o,'Invalid params');var p=l.getClientID();h.isTrue(p!==null&&p.length>0,'You need to call FB.init() with App ID first.');i.logEvent(p,m,n,o);},logPurchase:function(m,n,o){h.isTrue(k('allow_non_canvas_app_events',false)||l.isCanvasEnvironment(),'You can only use this function in Facebook Canvas environment');h.isNumber(m,'Invalid purchaseAmount');h.isString(n,'Invalid currency');h.maybeObject(o,'Invalid params');var p=l.getClientID();h.isTrue(p!==null&&p.length>0,'You need to call FB.init() with App ID first.');i.logPurchase(p,m,n,o);},activateApp:function(){h.isTrue(k('allow_non_canvas_app_events',false)||l.isCanvasEnvironment(),'You can only use this function in Facebook Canvas environment');var m=l.getClientID();h.isTrue(m!==null&&m.length>0,'You need to call FB.init() with App ID first.');i.activateApp(m);},EventNames:i.EventNames,ParameterNames:i.ParameterNames});},3); __d('resolveURI',[],function a(b,c,d,e,f,g){if(c.__markCompiled)c.__markCompiled();function h(i){if(!i)return window.location.href;i=i.replace(/&/g,'&').replace(/"/g,'"');var j=document.createElement('div');j.innerHTML='';return j.firstChild.href;}f.exports=h;},null); __d('sdk.Canvas.Environment',['sdk.RPC'],function a(b,c,d,e,f,g,h){if(c.__markCompiled)c.__markCompiled();function i(l){h.remote.getPageInfo(function(m){l(m.result);});}function j(l,m){h.remote.scrollTo({x:l||0,y:m||0});}h.stub('getPageInfo');h.stub('scrollTo');var k={getPageInfo:i,scrollTo:j};f.exports=k;},null); __d('sdk.fbt',[],function a(b,c,d,e,f,g){if(c.__markCompiled)c.__markCompiled();var h={_:function(i){return typeof i==='string'?i:i[0];}};f.exports=h;},null); __d('sdk.Dialog',['sdk.Canvas.Environment','sdk.Content','sdk.DOM','DOMEventListener','ObservableMixin','sdk.Runtime','Type','sdk.UA','sdk.fbt','sdk.feature'],function a(b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q){if(c.__markCompiled)c.__markCompiled();var r=30,s=590,t=500,u=240,v=575;function w(){if(q('dialog_resize_refactor',false)){var z=j.getViewportInfo();if(z.height&&z.width)return {width:Math.min(z.width,t),height:Math.min(z.height,s)};}return null;}var x=n.extend({constructor:function z(aa,ba){this.parent();this.id=aa;this.display=ba;this._e2e={};if(!y._dialogs){y._dialogs={};y._addOrientationHandler();}y._dialogs[aa]=this;this.trackEvent('init');},trackEvent:function(z,aa){if(this._e2e[z])return this;this._e2e[z]=aa||ES('Date','now',false);if(z=='close')this.inform('e2e:end',this._e2e);return this;},trackEvents:function(z){if(typeof z==='string')z=ES('JSON','parse',false,z);for(var aa in z)if(z.hasOwnProperty(aa))this.trackEvent(aa,z[aa]);return this;}},l),y={newInstance:function(z,aa){return new x(z,aa);},_dialogs:null,_lastYOffset:0,_overlayListeners:[],_loaderEl:null,_overlayEl:null,_stack:[],_active:null,_forceTabletStyle:null,_closeOnOverlayTap:null,get:function(z){return y._dialogs[z];},_findRoot:function(z){while(z){if(j.containsCss(z,'fb_dialog'))return z;z=z.parentNode;}},_createWWWLoader:function(z){z=z?z:460;return y.create({content:'
'+' '+'
'+'
'+' Facebook'+'
'+'
'+'
'+'',width:z});},_createMobileLoader:function(){var z;if(o.nativeApp()){z='
';}else if(y.isTabletStyle()){z='';}else z='
'+''+' '+' '+' '+' '+' '+' '+' '+'
'+' '+' '+'
'+' '+p._("Loading...")+'
'+'
'+'
'+'
';return y.create({classes:'loading'+(y.isTabletStyle()?' centered':''),content:z});},_restoreBodyPosition:function(){var z=document.body;if(y.isTabletStyle()){j.removeCss(z,'fb_reposition');}else j.removeCss(z,'fb_hidden');},_setDialogOverlayStyle:function(){if(!y._overlayEl)return;var z=j.getViewportInfo();y._overlayEl.style.minHeight=z.height||z.width?z.height+'px':null;y._overlayEl.style.top=z.scrollTop?z.scrollTop+'px':null;},_showTabletOverlay:function(z){if(!y.isTabletStyle())return;if(!y._overlayEl){y._overlayEl=document.createElement('div');y._overlayEl.setAttribute('id','fb_dialog_ipad_overlay');i.append(y._overlayEl,null);}y._setDialogOverlayStyle();if(y._closeOnOverlayTap){var aa=false;setTimeout(function(){return aa=true;},3000);var ba=k.add(y._overlayEl,'click',function(){if(aa)z();});y._overlayListeners.push(ba);}y._overlayEl.className='';},_hideTabletOverlay:function(){if(y.isTabletStyle()){y._overlayEl.className='hidden';ES(y._overlayListeners,'forEach',true,function(z){return z.remove();});y._overlayListeners=[];}},showLoader:function(z,aa){if(!z)z=function(){};var ba=function(){y._hideLoader();y._restoreBodyPosition();y._hideTabletOverlay();z();};y._showTabletOverlay(ba);if(!y._loaderEl)y._loaderEl=y._findRoot(o.mobile()?y._createMobileLoader():y._createWWWLoader(aa));var ca=document.getElementById('fb_dialog_loader_close');if(ca){j.removeCss(ca,'fb_hidden');var da=k.add(ca,'click',ba);y._overlayListeners.push(da);}y._makeActive(y._loaderEl);},setCloseOnOverlayTap:function(z){y._closeOnOverlayTap=!!z;},_hideLoader:function(){if(y._loaderEl&&y._loaderEl==y._active)y._loaderEl.style.top='-10000px';},_makeActive:function(z){y._setDialogSizes();y._lowerActive();y._active=z;if(m.isEnvironment(m.ENVIRONMENTS.CANVAS))h.getPageInfo(function(aa){y._centerActive(aa);});y._centerActive();},_lowerActive:function(){if(!y._active)return;y._active.style.top='-10000px';y._active=null;},_removeStacked:function(z){y._stack=ES(y._stack,'filter',true,function(aa){return aa!=z;});},_centerActive:function(z){var aa=y._active;if(!aa)return;var ba=j.getViewportInfo(),ca=parseInt(aa.offsetWidth,10),da=parseInt(aa.offsetHeight,10),ea=ba.scrollLeft+(ba.width-ca)/2,fa=(ba.height-da)/2.5;if(eaga)ha=ga;ha+=ba.scrollTop;if(o.mobile()){var ia=100,ja=document.body;if(y.isTabletStyle()){ia+=(ba.height-da)/2;j.addCss(ja,'fb_reposition');}else{j.addCss(ja,'fb_hidden');if(q('dialog_resize_refactor',false))ja.style.width='auto';ha=10000;}var ka=j.getByClass('fb_dialog_padding',aa);if(ka.length)ka[0].style.height=ia+'px';}aa.style.left=(ea>0?ea:0)+'px';aa.style.top=(ha>0?ha:0)+'px';},_setDialogSizes:function(){var z=arguments.length<=0||arguments[0]===undefined?false:arguments[0];if(!o.mobile())return;for(var aa in y._dialogs)if(y._dialogs.hasOwnProperty(aa)){var ba=document.getElementById(aa);if(ba){ba.style.width=y.getDefaultSize().width+'px';if(!z)ba.style.height=y.getDefaultSize().height+'px';}}},getDefaultSize:function(){if(o.mobile()){var z=w();if(z){if(j.getViewportInfo().width<=z.width)z.width=j.getViewportInfo().width-r;if(j.getViewportInfo().height<=z.height)z.height=j.getViewportInfo().height-r;return z;}if(o.ipad())return {width:t,height:s};if(o.android()){return {width:screen.availWidth,height:screen.availHeight};}else{var aa=window.innerWidth,ba=window.innerHeight,ca=aa/ba>1.2;return {width:aa,height:Math.max(ba,ca?screen.width:screen.height)};}}return {width:v,height:u};},_handleOrientationChange:function(){var z=q('dialog_resize_refactor',false)?j.getViewportInfo().width:screen.availWidth;y._availScreenWidth=z;if(y.isTabletStyle()){y._setDialogSizes(true);y._centerActive();y._setDialogOverlayStyle();}else{var aa=y.getDefaultSize().width;for(var ba in y._dialogs)if(y._dialogs.hasOwnProperty(ba)){var ca=document.getElementById(ba);if(ca)ca.style.width=aa+'px';}}},_addOrientationHandler:function(){if(!o.mobile())return;var z="onorientationchange" in window?'orientationchange':'resize';y._availScreenWidth=q('dialog_resize_refactor',false)?j.getViewportInfo().width:screen.availWidth;k.add(window,z,function(aa){return setTimeout(y._handleOrientationChange,50);});},create:function(z){z=z||{};var aa=document.createElement('div'),ba=document.createElement('div'),ca='fb_dialog';if(z.closeIcon&&z.onClose){var da=document.createElement('a');da.className='fb_dialog_close_icon';da.onclick=z.onClose;aa.appendChild(da);}ca+=' '+(z.classes||'');if(o.ie()){ca+=' fb_dialog_legacy';ES(['vert_left','vert_right','horiz_top','horiz_bottom','top_left','top_right','bottom_left','bottom_right'],'forEach',true,function(ga){var ha=document.createElement('span');ha.className='fb_dialog_'+ga;aa.appendChild(ha);});}else ca+=o.mobile()?' fb_dialog_mobile':' fb_dialog_advanced';if(z.content)i.append(z.content,ba);aa.className=ca;var ea=parseInt(z.width,10);if(!isNaN(ea))aa.style.width=ea+'px';ba.className='fb_dialog_content';aa.appendChild(ba);if(o.mobile()){var fa=document.createElement('div');fa.className='fb_dialog_padding';aa.appendChild(fa);}i.append(aa);if(z.visible)y.show(aa);return ba;},show:function(z){var aa=y._findRoot(z);if(aa){y._removeStacked(aa);y._hideLoader();y._makeActive(aa);y._stack.push(aa);if('fbCallID' in z)y.get(z.fbCallID).inform('iframe_show').trackEvent('show');}},hide:function(z){var aa=y._findRoot(z);y._hideLoader();if(aa==y._active){y._lowerActive();y._restoreBodyPosition();y._hideTabletOverlay();if('fbCallID' in z)y.get(z.fbCallID).inform('iframe_hide').trackEvent('hide');}},remove:function(z){z=y._findRoot(z);if(z){var aa=y._active==z;y._removeStacked(z);if(aa){y._hideLoader();if(y._stack.length>0){y.show(y._stack.pop());}else{y._lowerActive();y._restoreBodyPosition();y._hideTabletOverlay();}}else if(y._active===null&&y._stack.length>0)y.show(y._stack.pop());setTimeout(function(){z.parentNode.removeChild(z);},3000);}},isActive:function(z){var aa=y._findRoot(z);return aa&&aa===y._active;},setForceTabletStyle:function(z){y._forceTabletStyle=!!z;},isTabletStyle:function(){var z;if(!o.mobile())return false;if(y._forceTabletStyle)return true;if(q('dialog_resize_refactor',false)){var aa=w();z=aa&&(aa.height>=s||aa.width>=t);}else z=!!o.ipad();return z;}};f.exports=y;},null); __d('sdk.Frictionless',['sdk.Auth','sdk.api','sdk.Event','sdk.Dialog'],function a(b,c,d,e,f,g,h,i,j,k){if(c.__markCompiled)c.__markCompiled();var l={_allowedRecipients:{},_useFrictionless:false,_updateRecipients:function(){l._allowedRecipients={};i('/me/apprequestformerrecipients',function(m){if(!m||m.error)return;ES(m.data,'forEach',true,function(n){l._allowedRecipients[n.recipient_id]=true;});});},init:function(){l._useFrictionless=true;h.getLoginStatus(function(m){if(m.status=='connected')l._updateRecipients();});j.subscribe('auth.login',function(m){if(m.authResponse)l._updateRecipients();});},_processRequestResponse:function(m,n){return function(o){var p=o&&o.updated_frictionless;if(l._useFrictionless&&p)l._updateRecipients();if(o){if(!n&&o.frictionless){k._hideLoader();k._restoreBodyPosition();k._hideIPadOverlay();}delete o.frictionless;delete o.updated_frictionless;}m&&m(o);};},isAllowed:function(m){if(!m)return false;if(typeof m==='number')return m in l._allowedRecipients;if(typeof m==='string')m=m.split(',');m=ES(m,'map',true,function(p){return ES(String(p),'trim',true);});var n=true,o=false;ES(m,'forEach',true,function(p){n=n&&p in l._allowedRecipients;o=true;});return n&&o;}};j.subscribe('init:post',function(m){if(m.frictionlessRequests)l.init();});f.exports=l;},null); __d('sdk.Native',['Log','sdk.UA'],function a(b,c,d,e,f,g,h,i){if(c.__markCompiled)c.__markCompiled();var j='fbNativeReady',k={onready:function(l){if(!i.nativeApp()){h.error('FB.Native.onready only works when the page is rendered '+'in a WebView of the native Facebook app. Test if this is the '+'case calling FB.UA.nativeApp()');return;}if(window.__fbNative&&!this.nativeReady)ES('Object','assign',false,this,window.__fbNative);if(this.nativeReady){l();}else{var m=function(n){window.removeEventListener(j,m);this.onready(l);};window.addEventListener(j,m,false);}}};f.exports=k;},null); __d('sdk.UIServer',['sdk.Auth','sdk.Content','sdk.DOM','sdk.Dialog','sdk.Event','sdk.Frictionless','Log','sdk.Native','QueryString','sdk.RPC','sdk.Runtime','JSSDKConfig','sdk.UA','UrlMap','sdk.XD','createObjectFrom','sdk.feature','sdk.fbt','flattenObject','sdk.getContextType','guid','insertIframe','resolveURI'],function a(b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,aa,ba,ca,da){if(c.__markCompiled)c.__markCompiled();var ea={transform:function(ka){if(ka.params.display==='touch'&&ja.canIframe(ka.params)&&window.postMessage){ka.params.channel=ja._xdChannelHandler(ka.id,'parent');if(!t.nativeApp())ka.params.in_iframe=1;return ka;}else return ja.genericTransform(ka);},getXdRelation:function(ka){var la=ka.display;if(la==='touch'&&window.postMessage&&ka.in_iframe)return 'parent';return ja.getXdRelation(ka);}},fa={'stream.share':{size:{width:670,height:340},url:'sharer.php',transform:function(ka){if(!ka.params.u)ka.params.u=window.location.toString();ka.params.display='popup';return ka;}},apprequests:{transform:function(ka){ka=ea.transform(ka);ka.params.frictionless=m&&m._useFrictionless;if(ka.params.frictionless){if(m.isAllowed(ka.params.to)){ka.params.display='iframe';ka.params.in_iframe=true;ka.hideLoader=true;}ka.cb=m._processRequestResponse(ka.cb,ka.hideLoader);}ka.closeIcon=false;return ka;},getXdRelation:ea.getXdRelation},feed:ea,'permissions.oauth':{url:'dialog/oauth',size:{width:t.mobile()?null:475,height:t.mobile()?null:183},transform:function(ka){if(!r.getClientID()){n.error('FB.login() called before FB.init().');return;}if(h.getAuthResponse()&&!ka.params.scope&&!ka.params.auth_type){n.error('FB.login() called when user is already connected.');ka.cb&&ka.cb({status:r.getLoginStatus(),authResponse:h.getAuthResponse()});return;}var la=ka.cb,ma=ka.id;delete ka.cb;var na=ES('Object','keys',false,ES('Object','assign',false,ka.params.response_type?w(ka.params.response_type.split(',')):{},{token:true,signed_request:true})).join(',');if(ka.params.display==='async'){ES('Object','assign',false,ka.params,{client_id:r.getClientID(),origin:aa(),response_type:na,domain:location.hostname});ka.cb=h.xdResponseWrapper(la,h.getAuthResponse(),'permissions.oauth');}else ES('Object','assign',false,ka.params,{client_id:r.getClientID(),redirect_uri:da(ja.xdHandler(la,ma,'opener',h.getAuthResponse(),'permissions.oauth')),origin:aa(),response_type:na,domain:location.hostname});return ka;}},'auth.logout':{url:'logout.php',transform:function(ka){if(!r.getClientID()){n.error('FB.logout() called before calling FB.init().');}else if(!h.getAuthResponse()){n.error('FB.logout() called without an access token.');}else{ka.params.next=ja.xdHandler(ka.cb,ka.id,'parent',h.getAuthResponse(),'logout');return ka;}}},'login.status':{url:'dialog/oauth',transform:function(ka){var la=ka.cb,ma=ka.id;delete ka.cb;ES('Object','assign',false,ka.params,{client_id:r.getClientID(),redirect_uri:ja.xdHandler(la,ma,'parent',h.getAuthResponse(),'login_status'),origin:aa(),response_type:'token,signed_request,code',domain:location.hostname});return ka;}},pay:{size:{width:555,height:120},connectDisplay:'popup'}},ga={};function ha(ka,la){ga[la]=true;return function(ma){delete ga[la];ka(ma);};}function ia(ka){if(!x('should_force_single_dialog_instance',true))return false;var la=ka.method.toLowerCase();if(la==='pay'&&ka.display==='async')return true;return false;}var ja={Methods:fa,_loadedNodes:{},_defaultCb:{},_resultToken:'"xxRESULTTOKENxx"',genericTransform:function(ka){if(ka.params.display=='dialog'||ka.params.display=='iframe')ES('Object','assign',false,ka.params,{display:'iframe',channel:ja._xdChannelHandler(ka.id,'parent.parent')},true);return ka;},checkOauthDisplay:function(ka){var la=ka.scope||ka.perms||r.getScope();if(!la)return ka.display;var ma=la.split(/\s|,/g);for(var na=0;na2048;},getDisplayMode:function(ka,la){if(la.display==='hidden'||la.display==='none')return la.display;var ma=r.isEnvironment(r.ENVIRONMENTS.CANVAS)||r.isEnvironment(r.ENVIRONMENTS.PAGETAB);if(ma&&!la.display)return 'async';if(t.mobile()&&la.method!=='feed'||la.display==='touch')return 'touch';if(la.display=='iframe'||la.display=='dialog')if(!ja.canIframe(la)){n.error('"dialog" mode can only be used when the user is connected.');return 'popup';}if(ka.connectDisplay&&!ma)return ka.connectDisplay;return la.display||(ja.canIframe(la)?'dialog':'popup');},canIframe:function(ka){if(r.getAccessToken())return true;if(t.mobile()&&r.getLoggedIntoFacebook())return !!ka.iframe_test;return false;},getXdRelation:function(ka){var la=ka.display;if(la==='popup'||la==='touch')return 'opener';if(la==='dialog'||la==='iframe'||la==='hidden'||la==='none')return 'parent';if(la==='async')return 'parent.frames['+window.name+']';},popup:function(ka){var la=typeof window.screenX!='undefined'?window.screenX:window.screenLeft,ma=typeof window.screenY!='undefined'?window.screenY:window.screenTop,na=typeof window.outerWidth!='undefined'?window.outerWidth:document.documentElement.clientWidth,oa=typeof window.outerHeight!='undefined'?window.outerHeight:document.documentElement.clientHeight-22,pa=t.mobile()?null:ka.size.width,qa=t.mobile()?null:ka.size.height,ra=la<0?window.screen.width+la:la,sa=parseInt(ra+(na-pa)/2,10),ta=parseInt(ma+(oa-qa)/2.5,10),ua=[];if(pa!==null)ua.push('width='+pa);if(qa!==null)ua.push('height='+qa);ua.push('left='+sa);ua.push('top='+ta);ua.push('scrollbars=1');if(ka.name=='permissions.request'||ka.name=='permissions.oauth')ua.push('location=1,toolbar=0');ua=ua.join(',');var va;if(ka.post){va=window.open('about:blank',ka.id,ua);if(va){ja.setLoadedNode(ka,va,'popup');i.submitToTarget({url:ka.url,target:ka.id,params:ka.params});}}else{va=window.open(ka.url,ka.id,ua);if(va)ja.setLoadedNode(ka,va,'popup');}if(!va)return;if(ka.id in ja._defaultCb)ja._popupMonitor();},setLoadedNode:function(ka,la,ma){if(ma==='iframe')la.fbCallID=ka.id;la={node:la,type:ma,fbCallID:ka.id};ja._loadedNodes[ka.id]=la;},getLoadedNode:function(ka){var la=typeof ka=='object'?ka.id:ka,ma=ja._loadedNodes[la];return ma?ma.node:null;},hidden:function(ka){ka.className='FB_UI_Hidden';ka.root=i.appendHidden('');ja._insertIframe(ka);},iframe:function(ka){ka.className='FB_UI_Dialog';if(ka.params.iframe_test){k.setForceTabletStyle(true);k.setCloseOnOverlayTap(true);}var la=function(){var ma=ES('JSON','stringify',false,{error_code:4201,error_message:y._("User canceled the Dialog flow")});ja._triggerDefault(ka.id,ma);};ka.root=k.create({onClose:la,closeIcon:ka.closeIcon===undefined?true:ka.closeIcon,classes:k.isTabletStyle()?'centered':''});if(!ka.hideLoader)k.showLoader(la,ka.size.width);j.addCss(ka.root,'fb_dialog_iframe');ja._insertIframe(ka);},touch:function(ka){if(ka.params&&ka.params.in_iframe){if(ka.ui_created){k.showLoader(function(){ja._triggerDefault(ka.id,null);},0);}else ja.iframe(ka);}else if(t.nativeApp()&&!ka.ui_created){ka.frame=ka.id;o.onready(function(){ja.setLoadedNode(ka,o.open(ka.url+'#cb='+ka.frameName),'native');});ja._popupMonitor();}else if(!ka.ui_created)ja.popup(ka);},async:function(ka){ka.params.redirect_uri=location.protocol+'//'+location.host+location.pathname;delete ka.params.access_token;q.remote.showDialog(ka.params,function(la){var ma=la.result;if(ma&&ma.e2e){var na=k.get(ka.id);na.trackEvents(ma.e2e);na.trackEvent('close');delete ma.e2e;}ka.cb(ma);});},getDefaultSize:function(){return k.getDefaultSize();},_insertIframe:function(ka){ja._loadedNodes[ka.id]=false;var la=function(ma){if(ka.id in ja._loadedNodes)ja.setLoadedNode(ka,ma,'iframe');};if(ka.post){ca({url:'about:blank',root:ka.root,className:ka.className,width:ka.size.width,height:ka.size.height,id:ka.id,onInsert:la,onload:function(ma){i.submitToTarget({url:ka.url,target:ma.name,params:ka.params});}});}else ca({url:ka.url,root:ka.root,className:ka.className,width:ka.size.width,height:ka.size.height,id:ka.id,name:ka.frameName,onInsert:la});},_handleResizeMessage:function(ka,la){var ma=ja.getLoadedNode(ka);if(!ma)return;if(la.height)ma.style.height=la.height+'px';if(la.width)ma.style.width=la.width+'px';v.inform('resize.ack',la||{},'parent.frames['+ma.name+']');if(!k.isActive(ma)){k.show(ma);}else k._centerActive();},_triggerDefault:function(ka,la){var ma={frame:ka};if(la)ma.result=la;ja._xdRecv(ma,ja._defaultCb[ka]||function(){});},_popupMonitor:function(){var ka;for(var la in ja._loadedNodes)if(ja._loadedNodes.hasOwnProperty(la)&&la in ja._defaultCb){var ma=ja._loadedNodes[la];if(ma.type!='popup'&&ma.type!='native')continue;var na=ma.node;try{if(na.closed){ja._triggerDefault(la,null);}else ka=true;}catch(oa){}}if(ka&&!ja._popupInterval){ja._popupInterval=setInterval(ja._popupMonitor,100);}else if(!ka&&ja._popupInterval){clearInterval(ja._popupInterval);ja._popupInterval=null;}},_xdChannelHandler:function(ka,la){return v.handler(function(ma){var na=ja.getLoadedNode(ka);if(!na)return;if(ma.type=='resize'){ja._handleResizeMessage(ka,ma);}else if(ma.type=='hide'){k.hide(na);}else if(ma.type=='rendered'){var oa=k._findRoot(na);k.show(oa);}else if(ma.type=='fireevent')l.fire(ma.event);},la,true,null);},_xdNextHandler:function(ka,la,ma,na){if(na)ja._defaultCb[la]=ka;return v.handler(function(oa){ja._xdRecv(oa,ka);},ma)+'&frame='+la;},_xdRecv:function(ka,la){var ma=ja.getLoadedNode(ka.frame);if(ma)if(ma.close){try{ma.close();if(/iPhone.*Version\/(5|6)/.test(navigator.userAgent)&&RegExp.$1!=='5')window.focus();ja._popupCount--;}catch(na){}}else if(j.containsCss(ma,'FB_UI_Hidden')){setTimeout(function(){ma.parentNode.parentNode.removeChild(ma.parentNode);},3000);}else if(j.containsCss(ma,'FB_UI_Dialog'))k.remove(ma);delete ja._loadedNodes[ka.frame];delete ja._defaultCb[ka.frame];if(ka.e2e){var oa=k.get(ka.frame);oa.trackEvents(ka.e2e);oa.trackEvent('close');delete ka.e2e;}la(ka);},_xdResult:function(ka,la,ma,na){return (ja._xdNextHandler(function(oa){ka&&ka(oa.result&&oa.result!=ja._resultToken&&ES('JSON','parse',false,oa.result));},la,ma,na)+'&result='+encodeURIComponent(ja._resultToken));},xdHandler:function(ka,la,ma,na,oa){return ja._xdNextHandler(h.xdResponseWrapper(ka,na,oa),la,ma,true);}};q.stub('showDialog');f.exports=ja;},null); __d('sdk.ui',['Assert','sdk.Impressions','Log','sdk.PlatformVersioning','sdk.Runtime','sdk.UIServer','sdk.feature'],function a(b,c,d,e,f,g,h,i,j,k,l,m,n){if(c.__markCompiled)c.__markCompiled();function o(p,q){h.isObject(p);h.maybeFunction(q);if(l.getIsVersioned()){k.assertVersionIsSet();if(p.version){k.assertValidVersion(p.version);}else p.version=l.getVersion();}p=ES('Object','assign',false,{},p);if(!p.method){j.error('"method" is a required parameter for FB.ui().');return null;}if(p.method=='pay.prompt')p.method='pay';var r=p.method;if(p.redirect_uri){j.warn('When using FB.ui, you should not specify a redirect_uri.');delete p.redirect_uri;}if((r=='permissions.request'||r=='permissions.oauth')&&(p.display=='iframe'||p.display=='dialog'))p.display=m.checkOauthDisplay(p);var s=n('e2e_tracking',true);if(s)p.e2e={};var t=m.prepareCall(p,q||function(){});if(!t)return null;var u=t.params.display;if(u==='dialog'){u='iframe';}else if(u==='none')u='hidden';var v=m[u];if(!v){j.error('"display" must be one of "popup", '+'"dialog", "iframe", "touch", "async", "hidden", or "none"');return null;}if(s)t.dialog.subscribe('e2e:end',function(w){w.method=r;w.display=u;j.debug('e2e: %s',ES('JSON','stringify',false,w));i.log(114,{payload:w});});v(t);return t.dialog;}f.exports=o;},null); __d('legacy:fb.auth',['sdk.Auth','sdk.Cookie','sdk.Event','FB','Log','sdk.Runtime','sdk.SignedRequest','sdk.ui'],function a(b,c,d,e,f,g,h,i,j,k,l,m,n,o){if(c.__markCompiled)c.__markCompiled();k.provide('',{getLoginStatus:function(){return h.getLoginStatus.apply(h,arguments);},getAuthResponse:function(){return h.getAuthResponse();},getAccessToken:function(){return m.getAccessToken()||null;},getUserID:function(){return m.getUserID()||m.getCookieUserID();},login:function(p,q){if(q&&q.perms&&!q.scope){q.scope=q.perms;delete q.perms;l.warn('OAuth2 specification states that \'perms\' '+'should now be called \'scope\'. Please update.');}var r=m.isEnvironment(m.ENVIRONMENTS.CANVAS)||m.isEnvironment(m.ENVIRONMENTS.PAGETAB);o(babelHelpers._extends({method:'permissions.oauth',display:r?'async':'popup',domain:location.hostname},q||{}),p);},logout:function(p){o({method:'auth.logout',display:'hidden'},p);}});h.subscribe('logout',ES(j.fire,'bind',true,j,'auth.logout'));h.subscribe('login',ES(j.fire,'bind',true,j,'auth.login'));h.subscribe('authresponse.change',ES(j.fire,'bind',true,j,'auth.authResponseChange'));h.subscribe('status.change',ES(j.fire,'bind',true,j,'auth.statusChange'));j.subscribe('init:post',function(p){if(p.status)h.getLoginStatus();if(m.getClientID())if(p.authResponse){h.setAuthResponse(p.authResponse,'connected');}else if(m.getUseCookie()){var q=i.loadSignedRequest(),r;if(q){try{r=n.parse(q);}catch(s){i.clearSignedRequestCookie();}if(r&&r.user_id)m.setCookieUserID(r.user_id);}i.loadMeta();}});},3); __d('sdk.Canvas.IframeHandling',['DOMWrapper','sdk.RPC'],function a(b,c,d,e,f,g,h,i){if(c.__markCompiled)c.__markCompiled();var j=null,k;function l(){var p=h.getWindow().document,q=p.body,r=p.documentElement,s=Math.max(q.offsetTop,0),t=Math.max(r.offsetTop,0),u=q.scrollHeight+s,v=q.offsetHeight+s,w=r.scrollHeight+t,x=r.offsetHeight+t;return Math.max(u,v,w,x);}function m(p){if(typeof p!='object')p={};var q=0,r=0;if(!p.height){p.height=l();q=16;r=4;}if(!p.frame)p.frame=window.name||'iframe_canvas';if(k){var s=k.height,t=p.height-s;if(t<=r&&t>=-q)return false;}k=p;i.remote.setSize(p);return true;}function n(p,q){if(q===undefined&&typeof p==='number'){q=p;p=true;}if(p||p===undefined){if(j===null)j=setInterval(function(){m();},q||100);m();}else if(j!==null){clearInterval(j);j=null;}}i.stub('setSize');var o={setSize:m,setAutoGrow:n};f.exports=o;},null); __d('sdk.Canvas.Navigation',['sdk.RPC'],function a(b,c,d,e,f,g,h){if(c.__markCompiled)c.__markCompiled();function i(k){h.local.navigate=function(l){k({path:l});};h.remote.setNavigationEnabled(true);}h.stub('setNavigationEnabled');var j={setUrlHandler:i};f.exports=j;},null); __d('sdk.Canvas.Plugin',['Log','sdk.RPC','sdk.Runtime','sdk.UA','sdk.api'],function a(b,c,d,e,f,g,h,i,j,k,l){if(c.__markCompiled)c.__markCompiled();var m='CLSID:D27CDB6E-AE6D-11CF-96B8-444553540000',n='CLSID:444785F1-DE89-4295-863A-D46C3A781394',o=null,p=k.osx()&&k.osx.getVersionParts(),q=!(p&&p[0]>10&&p[1]>10&&(k.chrome()>=31||k.webkit()>=537.71||k.firefox()>=25));function r(ba){ba._hideunity_savedstyle={};ba._hideunity_savedstyle.left=ba.style.left;ba._hideunity_savedstyle.position=ba.style.position;ba._hideunity_savedstyle.width=ba.style.width;ba._hideunity_savedstyle.height=ba.style.height;ba.style.left='-10000px';ba.style.position='absolute';ba.style.width='1px';ba.style.height='1px';}function s(ba){if(ba._hideunity_savedstyle){ba.style.left=ba._hideunity_savedstyle.left;ba.style.position=ba._hideunity_savedstyle.position;ba.style.width=ba._hideunity_savedstyle.width;ba.style.height=ba._hideunity_savedstyle.height;}}function t(ba){ba._old_visibility=ba.style.visibility;ba.style.visibility='hidden';}function u(ba){ba.style.visibility=ba._old_visibility||'';delete ba._old_visibility;}function v(ba){var ca=ba.type?ba.type.toLowerCase():null,da=ca==='application/x-shockwave-flash'||ba.classid&&ba.classid.toUpperCase()==m;if(!da)return false;var ea=/opaque|transparent/i;if(ea.test(ba.getAttribute('wmode')))return false;for(var fa=0;fa1/l||m=='*'||~ES(m,'indexOf',true,i.getClientID()))return;setTimeout(p,30000);}function r(u){n=u;}function s(u){o.push(u);}var t={COLLECT_AUTOMATIC:k.AUTOMATIC,COLLECT_MANUAL:k.MANUAL,addStaticResource:s,setCollectionMode:r,_maybeSample:q};f.exports=t;},null); __d('legacy:fb.canvas.prefetcher',['FB','sdk.Canvas.Prefetcher','sdk.Event','sdk.Runtime'],function a(b,c,d,e,f,g,h,i,j,k){if(c.__markCompiled)c.__markCompiled();h.provide('Canvas.Prefetcher',i);j.subscribe('init:post',function(l){if(k.isEnvironment(k.ENVIRONMENTS.CANVAS))i._maybeSample();});},3); __d('legacy:fb.canvas.presence',['sdk.RPC','sdk.Event'],function a(b,c,d,e,f,g,h,i){if(c.__markCompiled)c.__markCompiled();i.subscribe(i.SUBSCRIBE,j);i.subscribe(i.UNSUBSCRIBE,k);h.stub('useFriendsOnline');function j(l,m){if(l!='canvas.friendsOnlineUpdated')return;if(m.length===1)h.remote.useFriendsOnline(true);}function k(l,m){if(l!='canvas.friendsOnlineUpdated')return;if(m.length===0)h.remote.useFriendsOnline(false);}},3); __d('legacy:fb.canvas.syncrequests',['sdk.RPC','sdk.Event'],function a(b,c,d,e,f,g,h,i){if(c.__markCompiled)c.__markCompiled();h.stub('initPendingSyncRequests');function j(k,l){if(k!='canvas.syncRequestUpdated')return;h.remote.initPendingSyncRequests();i.unsubscribe(i.SUBSCRIBE,j);}i.subscribe(i.SUBSCRIBE,j);},3); __d('legacy:fb.event',['FB','sdk.Event','sdk.Runtime','sdk.Scribe','sdk.feature'],function a(b,c,d,e,f,g,h,i,j,k,l){if(c.__markCompiled)c.__markCompiled();var m=[],n=null,o=l('event_subscriptions_log',false);h.provide('Event',{subscribe:function(p,q){if(o){m.push(p);if(!n)n=setTimeout(function(){k.log('jssdk_error',{appId:j.getClientID(),error:'EVENT_SUBSCRIPTIONS_LOG',extra:{line:0,name:'EVENT_SUBSCRIPTIONS_LOG',script:'N/A',stack:'N/A',message:m.sort().join(',')}});m.length=0;n=null;},o);}return i.subscribe(p,q);},unsubscribe:ES(i.unsubscribe,'bind',true,i)});},3); __d('legacy:fb.frictionless',['FB','sdk.Frictionless'],function a(b,c,d,e,f,g,h,i){if(c.__markCompiled)c.__markCompiled();h.provide('Frictionless',i);},3); __d('sdk.init',['sdk.Cookie','sdk.ErrorHandling','sdk.Event','sdk.Impressions','Log','ManagedError','sdk.PlatformVersioning','QueryString','sdk.Runtime','sdk.URI','sdk.feature'],function a(b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r){if(c.__markCompiled)c.__markCompiled();function s(u){var v=typeof u=='number'&&u>0||typeof u=='string'&&/^[0-9a-f]{21,}$|^[0-9]{1,21}$/.test(u);if(v)return u.toString();l.warn('Invalid App Id: Must be a number or numeric string representing '+'the application id.');return null;}function t(u){if(p.getInitialized())l.warn('FB.init has already been called - this could indicate a problem');if(p.getIsVersioned()){if(Object.prototype.toString.call(u)!=='[object Object]')throw new m('Invalid argument');if(u.authResponse)l.warn('Setting authResponse is not supported');if(!u.version)u.version=new q(location.href).getQueryData().sdk_version;n.assertValidVersion(u.version);p.setVersion(u.version);}else{if(/number|string/.test(typeof u)){l.warn('FB.init called with invalid parameters');u={apiKey:u};}u=ES('Object','assign',false,{status:true},u||{});}var v=s(u.appId||u.apiKey);if(v!==null)p.setClientID(v);if('scope' in u)p.setScope(u.scope);if(u.cookie){p.setUseCookie(true);if(typeof u.cookie==='string')h.setDomain(u.cookie);}if(u.kidDirectedSite)p.setKidDirectedSite(true);p.setInitialized(true);if(r('js_sdk_impression_on_load',true))k.log(115,{});j.fire('init:post',u);}setTimeout(function(){var u=/(connect\.facebook\.net|\.facebook\.com\/assets.php).*?#(.*)/;ES(ES('Array','from',false,fb_fif_window.document.getElementsByTagName('script')),'forEach',true,function(v){if(v.src){var w=u.exec(v.src);if(w){var x=o.decode(w[2]);for(var y in x)if(x.hasOwnProperty(y)){var z=x[y];if(z=='0')x[y]=0;}t(x);}}});if(window.fbAsyncInit&&!window.fbAsyncInit.hasRun){window.fbAsyncInit.hasRun=true;i.unguard(window.fbAsyncInit)();}},0);f.exports=t;},null); __d('legacy:fb.init',['FB','sdk.init'],function a(b,c,d,e,f,g,h,i){if(c.__markCompiled)c.__markCompiled();h.provide('',{init:i});},3); __d('legacy:fb.ui',['FB','sdk.ui'],function a(b,c,d,e,f,g,h,i){if(c.__markCompiled)c.__markCompiled();h.provide('',{ui:i});},3); __d('legacy:fb.versioned-sdk',['sdk.Runtime'],function a(b,c,d,e,f,g,h){if(c.__markCompiled)c.__markCompiled();h.setIsVersioned(true);},3); __d("runOnce",[],function a(b,c,d,e,f,g){if(c.__markCompiled)c.__markCompiled();function h(i){var j,k;return function(){if(!j){j=true;k=i();}return k;};}f.exports=h;},null); __d('XFBML',['Assert','sdk.DOM','Log','ObservableMixin','sdk.UA','runOnce'],function a(b,c,d,e,f,g,h,i,j,k,l,m){if(c.__markCompiled)c.__markCompiled();var n={},o={},p=0,q=new k();function r(y,z){return ES(y[z]+'','trim',true);}function s(y){return y.scopeName?y.scopeName+':'+y.nodeName:'';}function t(y){return n[r(y,'nodeName').toLowerCase()]||n[s(y).toLowerCase()];}function u(y){var z=ES(r(y,'className').split(/\s+/),'filter',true,function(aa){return o.hasOwnProperty(aa);});if(z.length===0)return undefined;if(y.getAttribute('fb-xfbml-state')||!y.childNodes||y.childNodes.length===0||y.childNodes.length===1&&y.childNodes[0].nodeType===3||y.children.length===1&&r(y.children[0],'className')==='fb-xfbml-parse-ignore')return o[z[0]];}function v(y){var z={};ES(ES('Array','from',false,y.attributes),'forEach',true,function(aa){z[r(aa,'name')]=r(aa,'value');});return z;}function w(y,z,aa){var ba=document.createElement('div');i.addCss(y,z+'-'+aa);ES(ES('Array','from',false,y.childNodes),'forEach',true,function(ca){ba.appendChild(ca);});ES(ES('Array','from',false,y.attributes),'forEach',true,function(ca){ba.setAttribute(ca.name,ca.value);});y.parentNode.replaceChild(ba,y);return ba;}function x(y,z,aa){h.isTrue(y&&y.nodeType&&y.nodeType===1&&!!y.getElementsByTagName,'Invalid DOM node passed to FB.XFBML.parse()');h.isFunction(z,'Invalid callback passed to FB.XFBML.parse()');var ba=++p;j.info('XFBML Parsing Start %s',ba);var ca=1,da=0,ea=function(){ca--;if(ca===0){j.info('XFBML Parsing Finish %s, %s tags found',ba,da);z();q.inform('render',ba,da);}h.isTrue(ca>=0,'onrender() has been called too many times');};ES(ES('Array','from',false,y.getElementsByTagName('*')),'forEach',true,function(ga){if(!aa&&ga.getAttribute('fb-xfbml-state'))return;if(ga.nodeType!==1)return;var ha=t(ga)||u(ga);if(!ha)return;if(l.ie()<9&&ga.scopeName)ga=w(ga,ha.xmlns,ha.localName);ca++;da++;var ia=new ha.ctor(ga,ha.xmlns,ha.localName,v(ga));ia.subscribe('render',m(function(){ga.setAttribute('fb-xfbml-state','rendered');ea();}));var ja=function(){if(ga.getAttribute('fb-xfbml-state')=='parsed'){q.subscribe('render.queue',ja);}else{ga.setAttribute('fb-xfbml-state','parsed');ia.process();}};ja();});q.inform('parse',ba,da);var fa=30000;setTimeout(function(){if(ca>0)j.warn('%s tags failed to render in %s ms',ca,fa);},fa);ea();}q.subscribe('render',function(){var y=q.getSubscribers('render.queue');q.clearSubscribers('render.queue');ES(y,'forEach',true,function(z){z();});});ES('Object','assign',false,q,{registerTag:function(y){var z=y.xmlns+':'+y.localName;h.isUndefined(n[z],z+' already registered');n[z]=y;o[y.xmlns+'-'+y.localName]=y;},parse:function(y,z){x(y||document.body,z||function(){},true);},parseNew:function(){x(document.body,function(){},false);}});f.exports=q;},null); __d('PluginPipe',['sdk.Content','sdk.feature','guid','insertIframe','Miny','ObservableMixin','JSSDKPluginPipeConfig','sdk.Runtime','sdk.UA','UrlMap','XFBML'],function a(b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r){if(c.__markCompiled)c.__markCompiled();var s=new m(),t=n.threshold,u=[];function v(){return !!(i('plugin_pipe',false)&&o.getSecure()!==undefined&&(p.chrome()||p.firefox())&&n.enabledApps[o.getClientID()]);}function w(){var y=u;u=[];if(y.length<=t){ES(y,'forEach',true,function(ba){k(ba.config);});return;}var z=y.length+1;function aa(){z--;if(z===0)x(y);}ES(y,'forEach',true,function(ba){var ca={};for(var da in ba.config)ca[da]=ba.config[da];ca.url=q.resolve('www',o.getSecure())+'/plugins/plugin_pipe_shell.php';ca.onload=aa;k(ca);});aa();}r.subscribe('parse',w);function x(y){var z=document.createElement('span');h.appendHidden(z);var aa={};ES(y,'forEach',true,function(fa){aa[fa.config.name]={plugin:fa.tag,params:fa.params};});var ba=ES('JSON','stringify',false,aa),ca=l.encode(ba);ES(y,'forEach',true,function(fa){var ga=document.getElementsByName(fa.config.name)[0];ga.onload=fa.config.onload;});var da=q.resolve('www',o.getSecure())+'/plugins/pipe.php',ea=j();k({url:'about:blank',root:z,name:ea,className:'fb_hidden fb_invisible',onload:function(){h.submitToTarget({url:da,target:ea,params:{plugins:ca.lengthka&&ja>ia){this._iframe.style.left=parseInt(i.getStyle(this._iframe,'width'),10)-ia+'px';this._isRepositioned=true;ma.type='reposition';}else if(this._isRepositioned&&la-ia!==0){this._iframe.style.left='0px';this._isRepositioned=false;ma.type='restore';}else return;u.sendToFacebook(this._iframe.name,{method:'xd/reposition',params:ES('JSON','stringify',false,ma)});},updateLift:function(){var ia=this._iframe.style.width===this._iframeOptions.root.style.width&&this._iframe.style.height===this._iframeOptions.root.style.height;i[ia?'removeCss':'addCss'](this._iframe,'fb_iframe_widget_lift');}},l);ha.getVal=da;ha.withParams=function(ia,ja){return ha.extend({getParams:function(){return ia;},getConfig:function(){return ja?ja:{};}});};f.exports=ha;},null); __d('PluginConfig',['sdk.feature'],function a(b,c,d,e,f,g,h){if(c.__markCompiled)c.__markCompiled();var i={messengerpreconfirmation:{mobile_fullsize:true},messengeraccountconfirmation:{mobile_fullsize:true},messengerbusinesslink:{mobile_fullsize:true},messengertoggle:{mobile_fullsize:true},messengermessageus:{mobile_fullsize:true},post:{fluid:h('fluid_embed',false),mobile_fullsize:true}};f.exports=i;},null); __d('PluginTags',[],function a(b,c,d,e,f,g){if(c.__markCompiled)c.__markCompiled();var h={composer:{action_type:'string',action_properties:'string'},create_event_button:{},follow:{href:'url',layout:'string',show_faces:'bool'},like:{href:'url',layout:'string',show_faces:'bool',share:'bool',action:'string',send:'bool'},like_box:{href:'string',show_faces:'bool',header:'bool',stream:'bool',force_wall:'bool',show_border:'bool',id:'string',connections:'string',profile_id:'string',name:'string'},page:{href:'string',hide_cta:'bool',hide_cover:'bool',small_header:'bool',adapt_container_width:'bool',show_facepile:'bool',show_posts:'bool',tabs:'string'},messengerpreconfirmation:{messenger_app_id:'string'},messengeraccountconfirmation:{messenger_app_id:'string',state:'string'},messengerbusinesslink:{messenger_app_id:'string',state:'string'},messengertoggle:{messenger_app_id:'string',token:'string'},messengermessageus:{messenger_app_id:'string',color:'string',size:'string'},page_events:{href:'url'},post:{href:'url',show_border:'bool'},profile_pic:{uid:'string',linked:'bool',href:'string',size:'string',facebook_logo:'bool'},send:{href:'url'},send_to_mobile:{max_rows:'string',show_faces:'bool',size:'string'}},i={subscribe:'follow',fan:'like_box',likebox:'like_box'};ES(ES('Object','keys',false,i),'forEach',true,function(j){h[j]=h[i[j]];});f.exports=h;},null); __d('sdk.Arbiter',[],function a(b,c,d,e,f,g){if(c.__markCompiled)c.__markCompiled();var h={BEHAVIOR_EVENT:'e',BEHAVIOR_PERSISTENT:'p',BEHAVIOR_STATE:'s'};f.exports=h;},null); __d('sdk.XFBML.Element',['sdk.DOM','Type','ObservableMixin'],function a(b,c,d,e,f,g,h,i,j){if(c.__markCompiled)c.__markCompiled();var k=i.extend({constructor:function(l){this.parent();this.dom=l;},fire:function(){this.inform.apply(this,arguments);},getAttribute:function(l,m,n){var o=h.getAttr(this.dom,l);return o?n?n(o):o:m;},_getBoolAttribute:function(l,m){var n=h.getBoolAttr(this.dom,l);return n===null?m:n;},_getPxAttribute:function(l,m){return this.getAttribute(l,m,function(n){var o=parseInt(n,10);return isNaN(o)?m:o;});},_getLengthAttribute:function(l,m){return this.getAttribute(l,m,function(n){if(n==='100%')return n;var o=parseInt(n,10);return isNaN(o)?m:o;});},_getAttributeFromList:function(l,m,n){return this.getAttribute(l,m,function(o){o=o.toLowerCase();return ES(n,'indexOf',true,o)>-1?o:m;});},isValid:function(){for(var l=this.dom;l;l=l.parentNode)if(l==document.body)return true;},clear:function(){h.html(this.dom,'');}},j);f.exports=k;},null); __d('sdk.XFBML.IframeWidget',['sdk.Arbiter','sdk.Auth','sdk.Content','sdk.DOM','sdk.Event','sdk.XFBML.Element','guid','insertIframe','QueryString','sdk.Runtime','sdk.ui','UrlMap','sdk.XD'],function a(b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t){if(c.__markCompiled)c.__markCompiled();var u=m.extend({_iframeName:null,_showLoader:true,_refreshOnAuthChange:false,_allowReProcess:false,_fetchPreCachedLoader:false,_visibleAfter:'load',_widgetPipeEnabled:false,_borderReset:false,_repositioned:false,getUrlBits:function(){throw new Error('Inheriting class needs to implement getUrlBits().');},setupAndValidate:function(){return true;},oneTimeSetup:function(){},getSize:function(){},getIframeName:function(){return this._iframeName;},getIframeTitle:function(){return 'Facebook Social Plugin';},getChannelUrl:function(){if(!this._channelUrl){var y=this;this._channelUrl=t.handler(function(z){y.fire('xd.'+z.type,z);},'parent.parent',true);}return this._channelUrl;},getIframeNode:function(){return this.dom.getElementsByTagName('iframe')[0];},arbiterInform:function(event,y,z){t.sendToFacebook(this.getIframeName(),{method:event,params:ES('JSON','stringify',false,y||{}),behavior:z||h.BEHAVIOR_PERSISTENT});},_arbiterInform:function(event,y,z){var aa='parent.frames["'+this.getIframeNode().name+'"]';t.inform(event,y,aa,z);},getDefaultWebDomain:function(){return s.resolve('www');},process:function(y){if(this._done){if(!this._allowReProcess&&!y)return;this.clear();}else this._oneTimeSetup();this._done=true;this._iframeName=this.getIframeName()||this._iframeName||n();if(!this.setupAndValidate()){this.fire('render');return;}if(this._showLoader)this._addLoader();k.addCss(this.dom,'fb_iframe_widget');if(this._visibleAfter!='immediate'){k.addCss(this.dom,'fb_hide_iframes');}else this.subscribe('iframe.onload',ES(this.fire,'bind',true,this,'render'));var z=this.getSize()||{},aa=this.getFullyQualifiedURL();if(z.width=='100%')k.addCss(this.dom,'fb_iframe_widget_fluid');this.clear();o({url:aa,root:this.dom.appendChild(document.createElement('span')),name:this._iframeName,title:this.getIframeTitle(),className:q.getRtl()?'fb_rtl':'fb_ltr',height:z.height,width:z.width,onload:ES(this.fire,'bind',true,this,'iframe.onload')});this._resizeFlow(z);this.loaded=false;this.subscribe('iframe.onload',ES(function(){this.loaded=true;if(!this._isResizeHandled)k.addCss(this.dom,'fb_hide_iframes');},'bind',true,this));},generateWidgetPipeIframeName:function(){v++;return 'fb_iframe_'+v;},getFullyQualifiedURL:function(){var y=this._getURL();y+='?'+p.encode(this._getQS());if(y.length>2000){y='about:blank';var z=ES(function(){this._postRequest();this.unsubscribe('iframe.onload',z);},'bind',true,this);this.subscribe('iframe.onload',z);}return y;},_getWidgetPipeShell:function(){return s.resolve('www')+'/common/widget_pipe_shell.php';},_oneTimeSetup:function(){this.subscribe('xd.resize',ES(this._handleResizeMsg,'bind',true,this));this.subscribe('xd.resize',ES(this._bubbleResizeEvent,'bind',true,this));this.subscribe('xd.resize.iframe',ES(this._resizeIframe,'bind',true,this));this.subscribe('xd.resize.flow',ES(this._resizeFlow,'bind',true,this));this.subscribe('xd.resize.flow',ES(this._bubbleResizeEvent,'bind',true,this));this.subscribe('xd.refreshLoginStatus',function(){i.getLoginStatus(function(){},true);});this.subscribe('xd.logout',function(){r({method:'auth.logout',display:'hidden'},function(){});});if(this._refreshOnAuthChange)this._setupAuthRefresh();if(this._visibleAfter=='load')this.subscribe('iframe.onload',ES(this._makeVisible,'bind',true,this));this.subscribe('xd.verify',ES(function(y){this.arbiterInform('xd/verify',y.token);},'bind',true,this));this.oneTimeSetup();},_makeVisible:function(){this._removeLoader();k.removeCss(this.dom,'fb_hide_iframes');this.fire('render');},_setupAuthRefresh:function(){i.getLoginStatus(ES(function(y){var z=y.status;l.subscribe('auth.statusChange',ES(function(aa){if(!this.isValid())return;if(z=='unknown'||aa.status=='unknown')this.process(true);z=aa.status;},'bind',true,this));},'bind',true,this));},_handleResizeMsg:function(y){if(!this.isValid())return;this._resizeIframe(y);this._resizeFlow(y);if(!this._borderReset){this.getIframeNode().style.border='none';this._borderReset=true;}this._isResizeHandled=true;this._makeVisible();},_bubbleResizeEvent:function(y){var z={height:y.height,width:y.width,pluginID:this.getAttribute('plugin-id')};l.fire('xfbml.resize',z);},_resizeIframe:function(y){var z=this.getIframeNode();if(y.reposition==="true")this._repositionIframe(y);y.height&&(z.style.height=y.height+'px');y.width&&(z.style.width=y.width+'px');this._updateIframeZIndex();},_resizeFlow:function(y){var z=this.dom.getElementsByTagName('span')[0];y.height&&(z.style.height=y.height+'px');y.width&&(z.style.width=y.width+'px');this._updateIframeZIndex();},_updateIframeZIndex:function(){var y=this.dom.getElementsByTagName('span')[0],z=this.getIframeNode(),aa=z.style.height===y.style.height&&z.style.width===y.style.width,ba=aa?'removeCss':'addCss';k[ba](z,'fb_iframe_widget_lift');},_repositionIframe:function(y){var z=this.getIframeNode(),aa=parseInt(k.getStyle(z,'width'),10),ba=k.getPosition(z).x,ca=k.getViewportInfo().width,da=parseInt(y.width,10);if(ba+da>ca&&ba>da){z.style.left=aa-da+'px';this.arbiterInform('xd/reposition',{type:'horizontal'});this._repositioned=true;}else if(this._repositioned){z.style.left='0px';this.arbiterInform('xd/reposition',{type:'restore'});this._repositioned=false;}},_addLoader:function(){if(!this._loaderDiv){k.addCss(this.dom,'fb_iframe_widget_loader');this._loaderDiv=document.createElement('div');this._loaderDiv.className='FB_Loader';this.dom.appendChild(this._loaderDiv);}},_removeLoader:function(){if(this._loaderDiv){k.removeCss(this.dom,'fb_iframe_widget_loader');if(this._loaderDiv.parentNode)this._loaderDiv.parentNode.removeChild(this._loaderDiv);this._loaderDiv=null;}},_getQS:function(){return ES('Object','assign',false,{api_key:q.getClientID(),locale:q.getLocale(),sdk:'joey',kid_directed_site:q.getKidDirectedSite(),ref:this.getAttribute('ref')},this.getUrlBits().params);},_getURL:function(){var y=this.getDefaultWebDomain(),z='';return y+'/plugins/'+z+this.getUrlBits().name+'.php';},_postRequest:function(){j.submitToTarget({url:this._getURL(),target:this.getIframeNode().name,params:this._getQS()});}}),v=0,w={};function x(){var y={};for(var z in w){var aa=w[z];y[z]={widget:aa.getUrlBits().name,params:aa._getQS()};}return y;}f.exports=u;},null); __d('sdk.XFBML.Comments',['sdk.Event','sdk.XFBML.IframeWidget','QueryString','sdk.Runtime','JSSDKConfig','sdk.UA','UrlMap'],function a(b,c,d,e,f,g,h,i,j,k,l,m,n){if(c.__markCompiled)c.__markCompiled();var o=i.extend({_visibleAfter:'immediate',_refreshOnAuthChange:true,setupAndValidate:function(){var p={channel_url:this.getChannelUrl(),colorscheme:this.getAttribute('colorscheme'),skin:this.getAttribute('skin'),numposts:this.getAttribute('num-posts',10),width:this._getLengthAttribute('width'),href:this.getAttribute('href'),permalink:this.getAttribute('permalink'),publish_feed:this.getAttribute('publish_feed'),order_by:this.getAttribute('order_by'),mobile:this._getBoolAttribute('mobile'),version:this.getAttribute('version')};if(!p.width&&!p.permalink)p.width=550;if(l.initSitevars.enableMobileComments&&m.mobile()&&p.mobile!==false){p.mobile=true;delete p.width;}if(!p.skin)p.skin=p.colorscheme;if(!p.href){p.migrated=this.getAttribute('migrated');p.xid=this.getAttribute('xid');p.title=this.getAttribute('title',document.title);p.url=this.getAttribute('url',document.URL);p.quiet=this.getAttribute('quiet');p.reverse=this.getAttribute('reverse');p.simple=this.getAttribute('simple');p.css=this.getAttribute('css');p.notify=this.getAttribute('notify');if(!p.xid){var q=ES(document.URL,'indexOf',true,'#');if(q>0){p.xid=encodeURIComponent(document.URL.substring(0,q));}else p.xid=encodeURIComponent(document.URL);}if(p.migrated)p.href=n.resolve('www')+'/plugins/comments_v1.php?'+'app_id='+k.getClientID()+'&xid='+encodeURIComponent(p.xid)+'&url='+encodeURIComponent(p.url);}else{var r=this.getAttribute('fb_comment_id');if(!r){r=j.decode(document.URL.substring(ES(document.URL,'indexOf',true,'?')+1)).fb_comment_id;if(r&&ES(r,'indexOf',true,'#')>0)r=r.substring(0,ES(r,'indexOf',true,'#'));}if(r){p.fb_comment_id=r;this.subscribe('render',ES(function(){if(!window.location.hash)window.location.hash=this.getIframeNode().id;},'bind',true,this));}}if(!p.version)p.version=k.getVersion();this._attr=p;return true;},oneTimeSetup:function(){this.subscribe('xd.sdk_event',function(p){h.fire(p.event,ES('JSON','parse',false,p.data));});},getSize:function(){if(!this._attr.permalink)return {width:this._attr.mobile?'100%':this._attr.width,height:100};},getUrlBits:function(){return {name:'comments',params:this._attr};},getDefaultWebDomain:function(){return n.resolve('www',true);}});f.exports=o;},null); __d('sdk.XFBML.CommentsCount',['ApiClient','sdk.DOM','sdk.XFBML.Element','sprintf'],function a(b,c,d,e,f,g,h,i,j,k){if(c.__markCompiled)c.__markCompiled();var l=j.extend({process:function(){i.addCss(this.dom,'fb_comments_count_zero');var m=this.getAttribute('href',window.location.href);h.scheduleBatchCall('/v2.1/'+encodeURIComponent(m),{fields:'share'},ES(function(n){var o=n.share&&n.share.comment_count||0;i.html(this.dom,k('%s',o));if(o>0)i.removeCss(this.dom,'fb_comments_count_zero');this.fire('render');},'bind',true,this));}});f.exports=l;},null); __d('safeEval',[],function a(b,c,d,e,f,g){if(c.__markCompiled)c.__markCompiled();function h(i,j){if(i===null||typeof i==='undefined')return;if(typeof i!=='string')return i;if(/^\w+$/.test(i)&&typeof window[i]==='function')return window[i].apply(null,j||[]);return Function('return eval("'+i.replace(/"/g,'\\"')+'");').apply(null,j||[]);}f.exports=h;},null); __d('sdk.Helper',['sdk.ErrorHandling','sdk.Event','UrlMap','safeEval','sprintf'],function a(b,c,d,e,f,g,h,i,j,k,l){if(c.__markCompiled)c.__markCompiled();var m={isUser:function(n){return n<2.2e+09||n>=1e+14&&n<=100099999989999||n>=8.9e+13&&n<=89999999999999||n>=6.000001e+13&&n<=60000019999999;},upperCaseFirstChar:function(n){if(n.length>0){return n.substr(0,1).toUpperCase()+n.substr(1);}else return n;},getProfileLink:function(n,o,p){if(!p&&n)p=l('%s/profile.php?id=%s',j.resolve('www'),n.uid||n.id);if(p)o=l('%s',p,o);return o;},invokeHandler:function(n,o,p){if(n)if(typeof n==='string'){h.unguard(k)(n,p);}else if(n.apply)h.unguard(n).apply(o,p||[]);},fireEvent:function(n,o){var p=o._attr.href;o.fire(n,p);i.fire(n,p,o);},executeFunctionByName:function(n){var o=Array.prototype.slice.call(arguments,1),p=n.split("."),q=p.pop(),r=window;for(var s=0;s"'\/]/g,i={'&':'&','<':'<','>':'>','"':'"',"'":''','/':'/'};function j(k){return k.replace(h,function(l){return i[l];});}f.exports=j;},null); __d('sdk.XFBML.Name',['ApiClient','escapeHTML','sdk.Event','sdk.XFBML.Element','sdk.Helper','Log','sdk.Runtime'],function a(b,c,d,e,f,g,h,i,j,k,l,m,n){if(c.__markCompiled)c.__markCompiled();var o=({}).hasOwnProperty,p=k.extend({process:function(){ES('Object','assign',false,this,{_uid:this.getAttribute('uid'),_firstnameonly:this._getBoolAttribute('first-name-only'),_lastnameonly:this._getBoolAttribute('last-name-only'),_possessive:this._getBoolAttribute('possessive'),_reflexive:this._getBoolAttribute('reflexive'),_objective:this._getBoolAttribute('objective'),_linked:this._getBoolAttribute('linked',true),_subjectId:this.getAttribute('subject-id')});if(!this._uid){m.error('"uid" is a required attribute for ');this.fire('render');return;}var q=[];if(this._firstnameonly){q.push('first_name');}else if(this._lastnameonly){q.push('last_name');}else q.push('name');if(this._subjectId){q.push('gender');if(this._subjectId==n.getUserID())this._reflexive=true;}j.monitor('auth.statusChange',ES(function(){if(!this.isValid()){this.fire('render');return true;}if(!this._uid||this._uid=='loggedinuser')this._uid=n.getUserID();if(!this._uid)return;h.scheduleBatchCall('/v1.0/'+this._uid,{fields:q.join(',')},ES(function(r){if(o.call(r,'error')){m.warn('The name is not found for ID: '+this._uid);return;}if(this._subjectId==this._uid){this._renderPronoun(r);}else this._renderOther(r);this.fire('render');},'bind',true,this));},'bind',true,this));},_renderPronoun:function(q){var r='',s=this._objective;if(this._subjectId){s=true;if(this._subjectId===this._uid)this._reflexive=true;}if(this._uid==n.getUserID()&&this._getBoolAttribute('use-you',true)){if(this._possessive){if(this._reflexive){r='your own';}else r='your';}else if(this._reflexive){r='yourself';}else r='you';}else switch(q.gender){case 'male':if(this._possessive){r=this._reflexive?'his own':'his';}else if(this._reflexive){r='himself';}else if(s){r='him';}else r='he';break;case 'female':if(this._possessive){r=this._reflexive?'her own':'her';}else if(this._reflexive){r='herself';}else if(s){r='her';}else r='she';break;default:if(this._getBoolAttribute('use-they',true)){if(this._possessive){if(this._reflexive){r='their own';}else r='their';}else if(this._reflexive){r='themselves';}else if(s){r='them';}else r='they';}else if(this._possessive){if(this._reflexive){r='his/her own';}else r='his/her';}else if(this._reflexive){r='himself/herself';}else if(s){r='him/her';}else r='he/she';break;}if(this._getBoolAttribute('capitalize',false))r=l.upperCaseFirstChar(r);this.dom.innerHTML=r;},_renderOther:function(q){var r='',s='';if(this._uid==n.getUserID()&&this._getBoolAttribute('use-you',true)){if(this._reflexive){if(this._possessive){r='your own';}else r='yourself';}else if(this._possessive){r='your';}else r='you';}else if(q){if(null===q.first_name)q.first_name='';if(null===q.last_name)q.last_name='';if(this._firstnameonly&&q.first_name!==undefined){r=i(q.first_name);}else if(this._lastnameonly&&q.last_name!==undefined)r=i(q.last_name);if(!r)r=i(q.name);if(r!==''&&this._possessive)r+='\'s';}if(!r)r=i(this.getAttribute('if-cant-see','Facebook User'));if(r){if(this._getBoolAttribute('capitalize',false))r=l.upperCaseFirstChar(r);if(q&&this._linked){s=l.getProfileLink(q,r,this.getAttribute('href',null));}else s=r;}this.dom.innerHTML=s;}});f.exports=p;},null); __d('sdk.XFBML.ShareButton',['IframePlugin','sdk.ui'],function a(b,c,d,e,f,g,h,i){'use strict';if(c.__markCompiled)c.__markCompiled();var j=h.extend({constructor:function(k,l,m,n){this.parent(k,l,m,n);this.subscribe('xd.shareTriggerIframe',function(o){var p=ES('JSON','parse',false,o.data);i({method:'share',href:p.href,iframe_test:true});});},getParams:function(){return {href:'url',layout:'string',type:'string'};}});f.exports=j;},null); __d('sdk.XFBML.Video',['Assert','sdk.Event','IframePlugin','ObservableMixin','sdk.XD'],function a(b,c,d,e,f,g,h,i,j,k,l){if(c.__markCompiled)c.__markCompiled();function m(p){'use strict';this.$VideoCache1=p.isMuted;this.$VideoCache2=p.volume;this.$VideoCache3=p.timePosition;this.$VideoCache4=p.duration;}m.prototype.update=function(p){'use strict';if(p.isMuted!==undefined)this.$VideoCache1=p.isMuted;if(p.volume!==undefined)this.$VideoCache2=p.volume;if(p.timePosition!==undefined)this.$VideoCache3=p.timePosition;if(p.duration!==undefined)this.$VideoCache4=p.duration;};m.prototype.isMuted=function(){'use strict';return this.$VideoCache1;};m.prototype.getVolume=function(){'use strict';return this.$VideoCache1?0:this.$VideoCache2;};m.prototype.getCurrentPosition=function(){'use strict';return this.$VideoCache3;};m.prototype.getDuration=function(){'use strict';return this.$VideoCache4;};function n(p,q,r){'use strict';this.$VideoController1=p;this.$VideoController2=q;this.$VideoController3=r;}n.prototype.play=function(){'use strict';l.sendToFacebook(this.$VideoController1,{method:'play',params:ES('JSON','stringify',false,{})});};n.prototype.pause=function(){'use strict';l.sendToFacebook(this.$VideoController1,{method:'pause',params:ES('JSON','stringify',false,{})});};n.prototype.seek=function(p){'use strict';h.isNumber(p,'Invalid argument');l.sendToFacebook(this.$VideoController1,{method:'seek',params:ES('JSON','stringify',false,{target:p})});};n.prototype.mute=function(){'use strict';l.sendToFacebook(this.$VideoController1,{method:'mute',params:ES('JSON','stringify',false,{})});};n.prototype.unmute=function(){'use strict';l.sendToFacebook(this.$VideoController1,{method:'unmute',params:ES('JSON','stringify',false,{})});};n.prototype.setVolume=function(p){'use strict';h.isNumber(p,'Invalid argument');l.sendToFacebook(this.$VideoController1,{method:'setVolume',params:ES('JSON','stringify',false,{volume:p})});};n.prototype.isMuted=function(){'use strict';return this.$VideoController3.isMuted();};n.prototype.getVolume=function(){'use strict';return this.$VideoController3.getVolume();};n.prototype.getCurrentPosition=function(){'use strict';return this.$VideoController3.getCurrentPosition();};n.prototype.getDuration=function(){'use strict';return this.$VideoController3.getDuration();};n.prototype.subscribe=function(event,p){'use strict';h.isString(event,'Invalid argument');h.isFunction(p,'Invalid argument');this.$VideoController2.subscribe(event,p);return {release:ES(function(){this.$VideoController2.unsubscribe(event,p);},'bind',true,this)};};var o=j.extend({constructor:function(p,q,r,s){this.parent(p,q,r,s);this._videoController=null;this._sharedObservable=null;this._sharedVideoCache=null;this.subscribe('xd.onVideoAPIReady',function(t){this._sharedObservable=new k();this._sharedVideoCache=new m(ES('JSON','parse',false,t.data));this._videoController=new n(this._iframeOptions.name,this._sharedObservable,this._sharedVideoCache);i.fire('xfbml.ready',{type:'video',id:s.id,instance:this._videoController});});this.subscribe('xd.stateChange',function(t){this._sharedObservable.inform(t.state);});this.subscribe('xd.cachedStateUpdateRequest',function(t){this._sharedVideoCache.update(ES('JSON','parse',false,t.data));});},getParams:function(){return {allowfullscreen:'bool',autoplay:'bool',controls:'bool',href:'url'};},getConfig:function(){return {fluid:true,full_width:true};}});f.exports=o;},null); __d('legacy:fb.xfbml',['Assert','sdk.Event','FB','IframePlugin','PluginConfig','PluginTags','XFBML','sdk.domReady','sdk.feature','wrapFunction','sdk.XFBML.Comments','sdk.XFBML.CommentsCount','sdk.XFBML.LoginButton','sdk.XFBML.Name','sdk.XFBML.ShareButton','sdk.XFBML.Video'],function a(b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q){if(c.__markCompiled)c.__markCompiled();var r={comments:c('sdk.XFBML.Comments'),comments_count:c('sdk.XFBML.CommentsCount'),login_button:c('sdk.XFBML.LoginButton'),name:c('sdk.XFBML.Name'),share_button:c('sdk.XFBML.ShareButton'),video:c('sdk.XFBML.Video')},s=p('plugin_tags_blacklist',[]);ES(ES('Object','keys',false,m),'forEach',true,function(u){if(ES(s,'indexOf',true,u)!==-1)return;n.registerTag({xmlns:'fb',localName:u.replace(/_/g,'-'),ctor:k.withParams(m[u],l[u])});});ES(ES('Object','keys',false,r),'forEach',true,function(u){if(ES(s,'indexOf',true,u)!==-1)return;n.registerTag({xmlns:'fb',localName:u.replace(/_/g,'-'),ctor:r[u]});});j.provide('XFBML',{parse:function(u){h.maybeXfbml(u,'Invalid argument');if(u&&u.nodeType===9)u=u.body;return n.parse.apply(null,arguments);}});n.subscribe('parse',ES(i.fire,'bind',true,i,'xfbml.parse'));n.subscribe('render',ES(i.fire,'bind',true,i,'xfbml.render'));i.subscribe('init:post',function(u){if(u.xfbml)setTimeout(q(ES(o,'bind',true,null,n.parse),'entry','init:post:xfbml.parse'),0);});h.define('Xfbml',function(u){return (u.nodeType===1||u.nodeType===9)&&typeof u.nodeName==='string';});try{if(document.namespaces&&!document.namespaces.item.fb)document.namespaces.add('fb');}catch(t){}},3); } }).call(global);})(window.inDapIF ? parent.window : window, window);} catch (e) {new Image().src="https:\/\/www.facebook.com\/" + 'common/scribe_endpoint.php?c=jssdk_error&m='+encodeURIComponent('{"error":"LOAD", "extra": {"name":"'+e.name+'","line":"'+(e.lineNumber||e.line)+'","script":"'+(e.fileName||e.sourceURL||e.script)+'","stack":"'+(e.stackTrace||e.stack)+'","revision":"2136283","namespace":"FB","message":"'+e.message+'"}}');} ================================================ FILE: public/js/app.js ================================================ angular.module('mean', ['ngCookies', 'ngResource', 'ui.router', 'ui.bootstrap', 'ui.route', 'mean.system', 'mean.articles', 'mean.auth','satellizer','angularFblogin']) .config(function ($authProvider) { $authProvider.twitter({ url: '/auth/twitter', authorizationEndpoint: 'https://api.twitter.com/oauth/authenticate', redirectUri: 'http://localhost:3000/auth/twitter/callback', oauthType: '1.0', popupOptions: { width: 495, height: 645 } }); $authProvider.google({ clientId: 'your google client id here', // google client id url: '/auth/google', redirectUri: 'http://localhost:3000/auth/google/callback' }); }); angular.module('mean.system', []); angular.module('mean.articles', []); angular.module('mean.auth', []); ================================================ FILE: public/js/config.js ================================================ //Setting up route angular.module('mean').config(['$stateProvider','$urlRouterProvider', function($stateProvider,$urlRouterProvider) { $urlRouterProvider.otherwise(function($injector, $location){ $injector.invoke(['$state', function($state) { $state.go('404'); }]); }); $stateProvider .state('home',{ url : '/', controller : 'IndexController', templateUrl: 'views/index.html' }) .state('SignIn',{ url : '/signin', templateUrl: 'views/users/signin.html' }) .state('SignUp',{ url : '/signup', templateUrl: 'views/users/signup.html' }) .state('articles',{ url : '/article', controller : 'ArticlesController', templateUrl: 'views/articles/list.html' }) .state('createArticle',{ url : '/article/create', controller : 'ArticlesController', templateUrl: 'views/articles/create.html' }) .state('editArticles',{ url : '/article/{articleId}/edit', controller : 'ArticlesController', templateUrl: 'views/articles/edit.html' }) .state('viewArticle',{ url : '/article/{articleId}', controller : 'ArticlesController', templateUrl: 'views/articles/view.html' }) .state('404',{ templateUrl: 'views/404.html' }) } ]); //Setting HTML5 Location Mode angular.module('mean').config(['$locationProvider', function ($locationProvider) { $locationProvider.html5Mode(true); }]); ================================================ FILE: public/js/controllers/articles.js ================================================ angular.module('mean.articles').controller('ArticlesController', ['$scope', '$stateParams', 'Global', 'Articles', '$state', function ($scope, $stateParams, Global, Articles, $state) { $scope.global = Global; $scope.create = function() { var article = new Articles({ title: this.title, content: this.content }); article.$save(function(response) { $state.go('viewArticle',{articleId : response.id}) }); this.title = ""; this.content = ""; }; $scope.remove = function(article) { if (article) { article.$remove(); for (var i in $scope.articles) { if ($scope.articles[i] === article) { $scope.articles.splice(i, 1); } } } else { $scope.article.$remove(); $state.go('articles'); } }; $scope.update = function() { var article = $scope.article; if (!article.updated) { article.updated = []; } article.updated.push(new Date().getTime()); article.$update(function() { $state.go('viewArticle',{articleId : article.id}) }); }; $scope.find = function() { Articles.query(function(articles) { $scope.articles = articles; }); }; $scope.findOne = function() { Articles.get({ articleId: $stateParams.articleId }, function(article) { $scope.article = article; }); }; $scope.find = function() { Articles.query(function(articles) { $scope.articles = articles; }); }; }]); ================================================ FILE: public/js/controllers/header.js ================================================ angular.module('mean.system').controller('HeaderController', ['$scope', 'Global', 'SignOut', '$state', function ($scope, Global, SignOut, $state) { $scope.global = Global; $scope.menu = [{ "title": "Articles", "state": "articles" }, { "title": "Create New Article", "state": "createArticle" }]; $scope.isCollapsed = false; $scope.SignOut = function(){ SignOut.get(function(response){ if(response.status === 'success'){ $scope.global = null; $state.go('home'); } }); } }]); ================================================ FILE: public/js/controllers/index.js ================================================ angular.module('mean.system').controller('IndexController', ['$scope', 'Global', function ($scope, Global) { $scope.global = Global; }]); ================================================ FILE: public/js/controllers/users/auth.js ================================================ angular.module('mean.auth').controller('socialAuth', ['$scope', 'Global','$state', '$fblogin', 'SocialAuth','$window','$auth', function ($scope, Global, $state, $fblogin, SocialAuth, $window, $auth) { $scope.global = Global; $scope.menu = [{ "title": "Articles", "state": "articles" }, { "title": "Create New Article", "state": "createArticle" }]; $scope.isCollapsed = false; $scope.fbAuth = function(){ $fblogin({ fbId: "102551953548872", permissions: 'email,user_birthday', fields: 'first_name,last_name,email,birthday,picture' }).then(function () { SocialAuth.FbLogin(FB.getAuthResponse()).then(function (response) { if(response.status === 'success' || 200){ $window.location.href = '/'; } }); }).catch(function () { $window.location.reload(); }) }; $scope.twitterAuth = function(){ $auth.authenticate('twitter').then(function(response) { if(response.status === 'success' || 200){ $window.location.href = '/'; } }); }; $scope.googleAuth = function(){ $auth.authenticate('google').then(function(response) { if(response.status === 'success' || 200){ $window.location.href = '/'; } }); }; }]); ================================================ FILE: public/js/controllers/users/signIn.js ================================================ angular.module('mean.auth').controller('signIn', ['$scope', '$window', 'Global', '$state', 'LogIn', function ($scope, $window, Global, $state, LogIn) { $scope.global = Global; $scope.signIn = function(user) { var logIn = new LogIn({ email: user.email, password: user.password }); logIn.$save(function(response) { if(response.status === 'success'){ $window.location.href = '/'; } }); }; }]); ================================================ FILE: public/js/controllers/users/signUp.js ================================================ angular.module('mean.auth').controller('signUp', ['$scope', '$window', 'Global','$state', 'SignUp', function ($scope, $window, Global, $state, SignUp) { $scope.global = Global; $scope.signUp = function(user) { var signUp = new SignUp({ name: user.name, email: user.email, username : user.userName, password : user.password }); signUp.$save(function(response) { if(response.status === 'success'){ $window.location.href = '/'; } }); }; }]); ================================================ FILE: public/js/directives.js ================================================ ================================================ FILE: public/js/filters.js ================================================ ================================================ FILE: public/js/init.js ================================================ angular.element(document).ready(function() { //Fixing facebook bug with redirect if (window.location.hash === "#_=_") { window.location.hash = "#!"; } //Then init the app angular.bootstrap(document, ['mean']); }); ================================================ FILE: public/js/services/articles.js ================================================ //Articles service used for articles REST endpoint angular.module('mean.articles').factory("Articles", ['$resource', function($resource) { return $resource('articles/:articleId', { articleId: '@id' }, { update: { method: 'PUT' } }); }]); ================================================ FILE: public/js/services/authenticate.js ================================================ angular.module('mean.auth').factory("SocialAuth", ['$http', function ($http) { return { FbLogin: function (token) { return $http.post('/auth/facebook/token', {"access_token": token.accessToken}) .then(function (res) { return res; }); } } }]); angular.module('mean.auth').service("SignOut", ['$resource', function($resource) { return $resource('/signout'); }]); angular.module('mean.auth').service("LogIn", ['$resource', function($resource) { return $resource('/users/session'); }]); angular.module('mean.auth').service("SignUp", ['$resource', function($resource) { return $resource('/users'); }]); ================================================ FILE: public/js/services/global.js ================================================ //Global service for global variables angular.module('mean.system').factory("Global", [ function() { var _this = this; _this._data = { user: window.user, authenticated: !! window.user }; return _this._data; } ]); ================================================ FILE: public/robots.txt ================================================ # robotstxt.org/ User-agent: * ================================================ FILE: public/views/404.html ================================================

ERROR #404

The link you followed may be broken, or the page may have been removed.

================================================ FILE: public/views/articles/create.html ================================================
================================================ FILE: public/views/articles/edit.html ================================================
================================================ FILE: public/views/articles/list.html ================================================
  • {{article.updatedAt | date:'medium'}} / {{article.User.name}}

    {{article.title}}

    {{article.content}}

No articles yet.
Why don't you Create One?

================================================ FILE: public/views/articles/view.html ================================================
{{article.updatedAt | date:'medium'}}/ {{article.User.name}}

{{article.title}}

{{article.content}}
================================================ FILE: public/views/header.html ================================================ ================================================ FILE: public/views/index.html ================================================

This is the home view

================================================ FILE: public/views/users/auth.html ================================================
================================================ FILE: public/views/users/signin.html ================================================ ================================================ FILE: public/views/users/signup.html ================================================
================================================ FILE: test/karma/karma.conf.js ================================================ // Karma configuration // Generated on Sat Oct 05 2013 22:00:14 GMT+0700 (ICT) module.exports = function(config) { config.set({ // base path, that will be used to resolve files and exclude basePath: '../../', // frameworks to use frameworks: ['jasmine'], // list of files / patterns to load in the browser files: [ 'public/lib/angular/angular.js', 'public/lib/angular-mocks/angular-mocks.js', 'public/lib/angular-cookies/angular-cookies.js', 'public/lib/angular-resource/angular-resource.js', 'public/lib/angular-route/angular-route.js', 'public/lib/angular-bootstrap/ui-bootstrap-tpls.js', 'public/lib/angular-bootstrap/ui-bootstrap.js', 'public/lib/angular-ui-utils/modules/route/route.js', 'public/js/app.js', 'public/js/config.js', 'public/js/directives.js', 'public/js/filters.js', 'public/js/services/global.js', 'public/js/services/articles.js', 'public/js/controllers/articles.js', 'public/js/controllers/index.js', 'public/js/controllers/header.js', 'public/js/init.js', 'test/karma/unit/**/*.js' ], // list of files to exclude exclude: [ ], // test results reporter to use // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage' //reporters: ['progress'], reporters: ['progress', 'coverage'], // coverage preprocessors: { // source files, that you wanna generate coverage for // do not include tests or libraries // (these files will be instrumented by Istanbul) 'public/js/controllers/*.js': ['coverage'], 'public/js/services/*.js': ['coverage'] }, coverageReporter: { type: 'html', dir: 'test/coverage/' }, // web server port port: 9876, // enable / disable colors in the output (reporters and logs) colors: true, // level of logging // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG logLevel: config.LOG_INFO, // enable / disable watching file and executing tests whenever any file changes autoWatch: true, // Start these browsers, currently available: // - Chrome // - ChromeCanary // - Firefox // - Opera // - Safari (only Mac) // - PhantomJS // - IE (only Windows) browsers: ['PhantomJS'], // If browser does not capture in given timeout [ms], kill it captureTimeout: 60000, // Continuous Integration mode // if true, it capture browsers, run tests and exit singleRun: true }); }; ================================================ FILE: test/karma/unit/controllers/articles.spec.js ================================================ (function() { 'use strict'; // Articles Controller Spec describe('MEAN controllers', function() { describe('ArticlesController', function() { // The $resource service augments the response object with methods for updating and deleting the resource. // If we were to use the standard toEqual matcher, our tests would fail because the test values would not match // the responses exactly. To solve the problem, we use a newly-defined toEqualData Jasmine matcher. // When the toEqualData matcher compares two objects, it takes only object properties into // account and ignores methods. beforeEach(function() { this.addMatchers({ toEqualData: function(expected) { return angular.equals(this.actual, expected); } }); }); // Load the controllers module beforeEach(module('mean')); // Initialize the controller and a mock scope var ArticlesController, scope, $httpBackend, $routeParams, $location; // The injector ignores leading and trailing underscores here (i.e. _$httpBackend_). // This allows us to inject a service but then attach it to a variable // with the same name as the service. beforeEach(inject(function($controller, $rootScope, _$location_, _$routeParams_, _$httpBackend_) { scope = $rootScope.$new(); ArticlesController = $controller('ArticlesController', { $scope: scope }); $routeParams = _$routeParams_; $httpBackend = _$httpBackend_; $location = _$location_; })); it('$scope.find() should create an array with at least one article object ' + 'fetched from XHR', function() { // test expected GET request $httpBackend.expectGET('articles').respond([{ title: 'An Article about MEAN', content: 'MEAN rocks!' }]); // run controller scope.find(); $httpBackend.flush(); // test scope value expect(scope.articles).toEqualData([{ title: 'An Article about MEAN', content: 'MEAN rocks!' }]); }); it('$scope.findOne() should create an array with one article object fetched ' + 'from XHR using a articleId URL parameter', function() { // fixture URL parament $routeParams.articleId = '525a8422f6d0f87f0e407a33'; // fixture response object var testArticleData = function() { return { title: 'An Article about MEAN', content: 'MEAN rocks!' }; }; // test expected GET request with response object $httpBackend.expectGET(/articles\/([0-9a-fA-F]{24})$/).respond(testArticleData()); // run controller scope.findOne(); $httpBackend.flush(); // test scope value expect(scope.article).toEqualData(testArticleData()); }); it('$scope.create() with valid form data should send a POST request ' + 'with the form input values and then ' + 'locate to new object URL', function() { // fixture expected POST data var postArticleData = function() { return { title: 'An Article about MEAN', content: 'MEAN rocks!' }; }; // fixture expected response data var responseArticleData = function() { return { _id: '525cf20451979dea2c000001', title: 'An Article about MEAN', content: 'MEAN rocks!' }; }; // fixture mock form input values scope.title = 'An Article about MEAN'; scope.content = 'MEAN rocks!'; // test post request is sent $httpBackend.expectPOST('articles', postArticleData()).respond(responseArticleData()); // Run controller scope.create(); $httpBackend.flush(); // test form input(s) are reset expect(scope.title).toEqual(''); expect(scope.content).toEqual(''); // test URL location to new object expect($location.path()).toBe('/articles/' + responseArticleData()._id); }); it('$scope.update() should update a valid article', inject(function(Articles) { // fixture rideshare var putArticleData = function() { return { _id: '525a8422f6d0f87f0e407a33', title: 'An Article about MEAN', to: 'MEAN is great!' }; }; // mock article object from form var article = new Articles(putArticleData()); // mock article in scope scope.article = article; // test PUT happens correctly $httpBackend.expectPUT(/articles\/([0-9a-fA-F]{24})$/).respond(); // testing the body data is out for now until an idea for testing the dynamic updated array value is figured out //$httpBackend.expectPUT(/articles\/([0-9a-fA-F]{24})$/, putArticleData()).respond(); /* Error: Expected PUT /articles\/([0-9a-fA-F]{24})$/ with different data EXPECTED: {"_id":"525a8422f6d0f87f0e407a33","title":"An Article about MEAN","to":"MEAN is great!"} GOT: {"_id":"525a8422f6d0f87f0e407a33","title":"An Article about MEAN","to":"MEAN is great!","updated":[1383534772975]} */ // run controller scope.update(); $httpBackend.flush(); // test URL location to new object expect($location.path()).toBe('/articles/' + putArticleData()._id); })); it('$scope.remove() should send a DELETE request with a valid articleId' + 'and remove the article from the scope', inject(function(Articles) { // fixture rideshare var article = new Articles({ _id: '525a8422f6d0f87f0e407a33' }); // mock rideshares in scope scope.articles = []; scope.articles.push(article); // test expected rideshare DELETE request $httpBackend.expectDELETE(/articles\/([0-9a-fA-F]{24})$/).respond(204); // run controller scope.remove(article); $httpBackend.flush(); // test after successful delete URL location articles lis //expect($location.path()).toBe('/articles'); expect(scope.articles.length).toBe(0); })); }); }); }()); ================================================ FILE: test/karma/unit/controllers/headers.spec.js ================================================ (function() { 'use strict'; describe('MEAN controllers', function() { describe('HeaderController', function() { // Load the controllers module beforeEach(module('mean')); var scope, HeaderController; beforeEach(inject(function($controller, $rootScope) { scope = $rootScope.$new(); HeaderController = $controller('HeaderController', { $scope: scope }); })); it('should expose some global scope', function() { expect(scope.global).toBeTruthy(); }); }); }); })(); ================================================ FILE: test/karma/unit/controllers/index.spec.js ================================================ (function() { 'use strict'; describe('MEAN controllers', function() { describe('IndexController', function() { // Load the controllers module beforeEach(module('mean')); var scope, IndexController; beforeEach(inject(function($controller, $rootScope) { scope = $rootScope.$new(); IndexController = $controller('IndexController', { $scope: scope }); })); it('should expose some global scope', function() { expect(scope.global).toBeTruthy(); }); }); }); })(); ================================================ FILE: test/mocha/controllers/articleControllerSpec.js ================================================ /** * Created by Ahmed Hassan on 12/11/15. * Set of tests demonstrating how controllers can be tested by stubbing out database dependency * The tests are pretty basic, intended for demonstration of technique */ "use strict"; var proxyquire = require('proxyquire').noCallThru(), dbStub = {}, article = proxyquire('../../../app/controllers/articles', { '../../config/sequelize': dbStub }), chai = require('chai'), Promise = require('sequelize').Promise; chai.should(); var expect = chai.expect; var req = {}; var res = {}; describe('Articles Controller', function () { describe('Create Article', function () { beforeEach(function () { req.user = {id: 1}; req.body = { id: 1, title: 'test title', content: 'this is test content to test functionality of Create Article' }; }); it('should return article on successful creation', function (done) { dbStub.Article = { create: function (obj) { return Promise.resolve(obj); } }; res.jsonp = function (article) { article.should.have.property('id'); article.should.have.property('title'); article.should.have.property('content'); article.should.have.property('UserId'); done(); }; article.create(req, res); }); it('should return error if Article could not be created', function (done) { dbStub.Article = { create: function (obj) { return Promise.resolve(); } }; res.send = function (path, err) { path.should.equal('users/signup'); err.should.have.property('errors'); done(); }; article.create(req, res); }); it('should return error if error occurs while executing query', function (done) { dbStub.Article = { create: function (obj) { return Promise.reject('error while executing query'); } }; res.send = function (path, err) { path.should.equal('users/signup'); err.should.have.property('errors'); err.should.have.property('status'); err.status.should.equal(500); done(); }; article.create(req, res); }); }); describe('Update Article', function () { beforeEach(function () { req = { article: { id: 1, title: 'test title', content: 'this is test content to test functionality of Create Article', UserId: 1, updateAttributes: function () { var updatedArticle = { id: 1, title: 'test title updated', content: 'this is updated test content to test functionality of Create Article', UserId: 1 }; return Promise.resolve(updatedArticle); } }, body: { title: 'test title updated', content: 'this is updated test content to test functionality of Create Article' } } }); it('should return updated article on success', function (done) { res.jsonp = function (updatedArticle) { updatedArticle.should.have.property('id'); updatedArticle.should.have.property('title'); updatedArticle.title.should.equal('test title updated'); updatedArticle.should.have.property('content'); updatedArticle.content.should.equal('this is updated test content to test functionality of Create Article'); updatedArticle.should.have.property('UserId'); done(); }; article.update(req, res); }); it('should return error if error occurs while executing query', function (done) { req.article.updateAttributes = function () { // note: the rejection value here is symbolic, // the key thing is that the same error should be propagated ahead return Promise.reject('error occurred while executing query'); }; res.render = function (err, obj) { err.should.equal('error'); obj.should.have.property('error'); obj.should.have.property('status'); obj.error.should.equal('error occurred while executing query'); obj.status.should.equal(500); done(); }; article.update(req, res); }); }); describe('Destroy Article', function () { beforeEach(function () { req = { article: { id: 1, title: 'test title', content: 'this is test content to test functionality of destroy Article', UserId: 1, destroy: function () { return Promise.resolve(); } } } }); it('should return destroyed article on successful destroy', function (done) { res.jsonp = function (article) { article.should.have.property('id'); article.should.have.property('title'); article.title.should.equal('test title'); article.should.have.property('content'); article.content.should.equal('this is test content to test functionality of destroy Article'); article.should.have.property('UserId'); done(); }; article.destroy(req, res); }); it('should return error if error occurs while executing query', function (done) { req.article.destroy = function () { return Promise.reject('error occurred while executing query'); }; res.render = function (err, obj) { err.should.equal('error'); obj.should.have.property('error'); obj.should.have.property('status'); obj.error.should.equal('error occurred while executing query'); obj.status.should.equal(500); done(); }; article.destroy(req, res); }); }); describe('Fetch all articles', function () { it('should return the received list of articles on successful query', function (done) { dbStub.Article = { findAll: function (obj) { var Articles = [{ id: 1, title: 'test', content: 'this is test content', UserId: 1 },{ id: 2, title: 'test', content: 'this is test content', UserId: 1 }]; return Promise.resolve(Articles); } }; dbStub.User = {id: 1}; res.jsonp = function (articles) { articles[0].should.have.property('id'); articles[0].should.have.property('title'); articles[0].title.should.equal('test'); articles[0].should.have.property('content'); articles[0].content.should.equal('this is test content'); articles[0].should.have.property('UserId'); done(); }; article.all(req, res); }); it('should return error if error occurs while executing query', function (done) { dbStub.Article = { findAll: function (obj) { return Promise.reject('error occurred while executing query'); } }; dbStub.User = {id: 1}; res.render = function (err, obj) { err.should.equal('error'); obj.should.have.property('error'); obj.should.have.property('status'); obj.error.should.equal('error occurred while executing query'); obj.status.should.equal(500); done(); }; article.all(req, res); }); }); describe('Has Authorization', function () { it('should give error if user is not authorized', function (done) { req = { article: { User: { id: 1 } }, user: { id: 2 } }, res.send = function (httpStatus, msg) { httpStatus.should.equal(401); msg.should.equal('User is not authorized'); done(); }; article.hasAuthorization(req, res); }); }); }); ================================================ FILE: test/mocha/controllers/usersControllerSpec.js ================================================ 'use strict'; /* Users controller tests */ ================================================ FILE: test/mocha/models/userModelSpec.js ================================================ 'use strict'; /** * Module dependencies. */ var chai = require('chai'), expect = chai.expect, _ = require('lodash'), winston = require('../../../config/winston'), User = require('../../../app/models/user'); chai.should(); //The tests describe('User model', function() { var userModel; before(function() { var sequelizeStub = { define: function(modelName, fields, properties){ return { name : modelName, fields: fields, properties: properties }; } }; var datatypesStub = { STRING: 'string', INTEGER: 'integer' }; userModel = User(sequelizeStub, datatypesStub); }); describe('name', function(){ it('should be equal to: User', function(){ userModel.name.should.equal('User'); }); }); describe('data', function() { it('should have username', function() { userModel.fields.username.should.exist.and.equal('string'); }); it('should have email', function() { userModel.fields.email.should.exist.and.equal('string'); }); it('should have hashedPassword', function() { userModel.fields.hashedPassword.should.exist.and.equal('string'); }); }); describe('properties', function(){ describe('instance methods', function(){ describe('makeSalt', function(){ it('should generate salt of length 16', function(){ var salt = userModel.properties.instanceMethods.makeSalt(); salt.should.be.a('string').with.length(24); }); }); describe('encryptPassword', function(){ it('should return empty if password is undefined', function(){ var encryptedPassword = userModel.properties.instanceMethods.encryptPassword(undefined,'salt'); encryptedPassword.should.be.a('string').with.length(0); }); it('should return empty if salt is undefined', function(){ var encryptedPassword = userModel.properties.instanceMethods.encryptPassword('password'); encryptedPassword.should.be.a('string').with.length(0); }); it('should return encrypted password if both password and salt are supplied', function(){ var encryptedPassword = userModel.properties.instanceMethods.encryptPassword('password','salt'); encryptedPassword.should.be.a('string').with.length(88); }); }); describe('authenticate', function(){ it('should return true if password is correct', function(){ var authResult = userModel.properties.instanceMethods.authenticate.call({ salt: 'salt', hashedPassword: userModel.properties.instanceMethods.encryptPassword('password','salt'), encryptPassword: userModel.properties.instanceMethods.encryptPassword }, 'password'); authResult.should.equal(true); }); it('should return false if password is incorrect', function(){ var authResult = userModel.properties.instanceMethods.authenticate.call({ salt: 'salt', hashedPassword: userModel.properties.instanceMethods.encryptPassword('password','salt'), encryptPassword: userModel.properties.instanceMethods.encryptPassword }, 'NOTpassword'); authResult.should.equal(false); }); }); }); }); after(function(done) { done(); }); });