<commit_before>import path from 'path';
import process from 'process';
import webpack from "webpack";
import { WebpackManifestPlugin } from "webpack-manifest-plugin";
import MiniCssExtractPlugin from "mini-css-extract-plugin";
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
import TerserPlugin from "terser-webpack-plugin";

// Check for production mode
const PROD = process.env.NODE_ENV === "production";

const src = path.resolve(__dirname, 'web', 'resources');
const dep = path.join(src, 'node_modules');
const dst = path.resolve(__dirname, 'web', 'static');

export default {
    mode: PROD ? "production" : "development",
    // We're creating a web application
    target: 'web',
    context: src,
    entry: {
        'advanced-search': './js/advanced-search.js',
        'balance-chart': './js/balance-chart.js',
        'lazy-load-select': './js/lazy-load-select.js',
        'mac-address-input': './js/mac-address-input.js',
        'confirmable-error': './js/confirmable-error.ts',
        'main': './main.js',
        'navigation': './js/navigation.js',
        'tooltip': './js/tooltip.js',
        'table-fixed-header': './js/table-fixed-header.js',
        'traffic-graph': './js/traffic-graph.js',
        'transaction-chart': './js/transaction-chart.js',
        'transaction-form': './js/transaction-form.js',
        'unlimited-end-date': './js/unlimited-end-date.js',
        'select-multiple-improvement': './js/select-multiple-improvement.js',
        'tab-anchor': './js/tab-anchor.ts',
        'rooms-table': './js/rooms-table.ts',
    },
    output: {
        path: dst,
        publicPath: '',
        filename: `[name].[chunkhash].${PROD ? 'min.js' : 'js'}`,
        hashFunction: 'md5',
        hashDigest: 'hex',
        hashDigestLength: 32,
        pathinfo: !PROD,
    },
    watchOptions: {
        aggregateTimeout: 1000,
        ignored: [
            dep,
        ],
    },
    devtool: PROD ? "source-map" : "eval-source-map",
    resolve: {
        symlinks: false,
        extensions: ['.js', '.ts'],
    },
    optimization: {
        minimizer: PROD ? [
            // Compress JavaScript
            new TerserPlugin({
                parallel: true,
            }),
        ] : [],
        runtimeChunk: {
            name: "runtime",
        },
        splitChunks: {
            cacheGroups: {
                vendor: {
                    name: "vendor",
                    chunks: "all",
                    enforce: true,
                    test: dep,
                },
            },
        },
        moduleIds: 'deterministic',
    },
    plugins: [
        // Clean the destination
        new CleanWebpackPlugin(),
        // Put CSS into a separate file
        new MiniCssExtractPlugin({
            filename: '[name].[contenthash].css',
            chunkFilename: '[id].[contenthash].css',
        }),
        // Generate a manifest file, that maps entries and assets to their
        // output file.
        new WebpackManifestPlugin({}),
    ].concat(PROD ? [
        // PROD plugins
    ] : [
        // !PROD plugins
    ]),
    module: {
        rules: [
            // Use the source-map-loader to reuse existing source maps, that
            // are provided by dependencies
            {
                test: /\.[j]sx?$/,  // TODO look at how we can deal with this and typescript
                use: ["source-map-loader"],
                enforce: "pre",
                include: dep,
            },
            {
                test: /\.tsx?$/,
                use: 'ts-loader',
                exclude: dep,
            },
            // Expose our table module as a global variable.
            // Functions from this module are referenced through
            // data-*-attributes for use by bootstrap-table.
            // This is necessary because `bootstrap-table` requires
            // the referenced function to be globally accessible, like
            // `window.fooFormatter`, and does not import it from somewhere.
            {
                test: path.join(src, 'js', 'table.js'),
                use: {
                    loader: "expose-loader",
                    options: {
                        exposes: "table",
                    },
                },
            },
            // Inject jQuery import into bootstrap-datepicker locales
            {
                test: /\.js$/,
                use: {
                    loader: "imports-loader",
                    options: {
                        type: 'commonjs',
                        imports: {
                            'moduleName': 'jquery',
                            'name': 'jQuery',
                        }
                    },
                },
                include: path.join(dep, "bootstrap-datepicker", "dist", "locales"),
            },
            // Transpile modern JavaScript for older browsers.
            {
                test: /\.jsx?$/,
                exclude: dep,
                use: {
                    loader: 'babel-loader',
                    options: {
                        cacheDirectory: true,
                        // Use the recommended preset-env
                        presets: [
                            ['@babel/preset-env', {
                                targets: {
                                    browsers: [
                                        'IE 11',
                                        'FF 52',
                                        'Chrome 49',
                                    ],
                                },
                                // Let webpack handle modules
                                modules: false,
                                forceAllTransforms: PROD,
                            }],
                        ],
                        // Import the babel runtime instead of inlining it in
                        // every file.
                        plugins: [
                            ['@babel/plugin-transform-runtime', {
                                helpers: false,
                                regenerator: true,
                                useESModules: true,
                            }],
                        ],
                    },
                },
            },
            // Handle CSS
            {
                test: /\.css$/,
                use: [
                    MiniCssExtractPlugin.loader,
                    "css-loader",
                ],
            },
            // Handle other assets
            {
                test: [
                    /\.(?:gif|ico|jpg|png|svg)$/, // Images
                    /\.(?:eot|otf|ttf|woff|woff2)$/, // Fonts
                    /\.(?:xml)$/, // static XML initial for openSearch
                ],
                exclude: dep,
                use: {
                    loader: 'file-loader',
                    options: {
                        name: "[path][name].[hash].[ext]",
                        publicPath: './',
                        esModule: false,
                    },
                },
            },
        ],
    },
};
<commit_msg>use `main` dependency to reduce bundle size<commit_after>import path from 'path';
import process from 'process';
import webpack from "webpack";
import { WebpackManifestPlugin } from "webpack-manifest-plugin";
import MiniCssExtractPlugin from "mini-css-extract-plugin";
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
import TerserPlugin from "terser-webpack-plugin";

// Check for production mode
const PROD = process.env.NODE_ENV === "production";

const src = path.resolve(__dirname, 'web', 'resources');
const dep = path.join(src, 'node_modules');
const dst = path.resolve(__dirname, 'web', 'static');

export default {
    mode: PROD ? "production" : "development",
    // We're creating a web application
    target: 'web',
    context: src,
    entry: {
        'main': './main.js',
        'advanced-search': {
            import: './js/advanced-search.js',
            dependOn: 'main',
        },
        'balance-chart': {
            import: './js/balance-chart.js',
            dependOn: 'main',
        },
        'lazy-load-select': {
            import: './js/lazy-load-select.js',
            dependOn: 'main',
        },
        'mac-address-input': {
            import: './js/mac-address-input.js',
            dependOn: 'main',
        },
        'confirmable-error': {
            import: './js/confirmable-error.ts',
            dependOn: 'main',
        },
        'navigation': {
            import: './js/navigation.js',
            dependOn: 'main',
        },
        'tooltip': {
            import: './js/tooltip.js',
            dependOn: 'main',
        },
        'table-fixed-header': {
            import: './js/table-fixed-header.js',
            dependOn: 'main',
        },
        'traffic-graph': {
            import: './js/traffic-graph.js',
            dependOn: 'main',
        },
        'transaction-chart': {
            import: './js/transaction-chart.js',
            dependOn: 'main',
        },
        'transaction-form': {
            import: './js/transaction-form.js',
            dependOn: 'main',
        },
        'unlimited-end-date': {
            import: './js/unlimited-end-date.js',
            dependOn: 'main',
        },
        'select-multiple-improvement': {
            import: './js/select-multiple-improvement.js',
            dependOn: 'main',
        },
        'tab-anchor': {
            import: './js/tab-anchor.ts',
            dependOn: 'main',
        },
        'rooms-table': {
            import: './js/rooms-table.ts',
            dependOn: 'main',
        },
    },
    output: {
        path: dst,
        publicPath: '',
        filename: `[name].[chunkhash].${PROD ? 'min.js' : 'js'}`,
        hashFunction: 'md5',
        hashDigest: 'hex',
        hashDigestLength: 32,
        pathinfo: !PROD,
    },
    watchOptions: {
        aggregateTimeout: 1000,
        ignored: [
            dep,
        ],
    },
    devtool: PROD ? "source-map" : "eval-source-map",
    resolve: {
        symlinks: false,
        extensions: ['.js', '.ts'],
    },
    optimization: {
        minimizer: PROD ? [
            // Compress JavaScript
            new TerserPlugin({
                parallel: true,
            }),
        ] : [],
        runtimeChunk: {
            name: "runtime",
        },
        splitChunks: {
            cacheGroups: {
                vendor: {
                    name: "vendor",
                    chunks: "all",
                    enforce: true,
                    test: dep,
                },
            },
        },
        moduleIds: 'deterministic',
    },
    plugins: [
        // Clean the destination
        new CleanWebpackPlugin(),
        // Put CSS into a separate file
        new MiniCssExtractPlugin({
            filename: '[name].[contenthash].css',
            chunkFilename: '[id].[contenthash].css',
        }),
        // Generate a manifest file, that maps entries and assets to their
        // output file.
        new WebpackManifestPlugin({}),
    ].concat(PROD ? [
        // PROD plugins
    ] : [
        // !PROD plugins
    ]),
    module: {
        rules: [
            // Use the source-map-loader to reuse existing source maps, that
            // are provided by dependencies
            {
                test: /\.[j]sx?$/,  // TODO look at how we can deal with this and typescript
                use: ["source-map-loader"],
                enforce: "pre",
                include: dep,
            },
            {
                test: /\.tsx?$/,
                use: 'ts-loader',
                exclude: dep,
            },
            // Expose our table module as a global variable.
            // Functions from this module are referenced through
            // data-*-attributes for use by bootstrap-table.
            // This is necessary because `bootstrap-table` requires
            // the referenced function to be globally accessible, like
            // `window.fooFormatter`, and does not import it from somewhere.
            {
                test: path.join(src, 'js', 'table.js'),
                use: {
                    loader: "expose-loader",
                    options: {
                        exposes: "table",
                    },
                },
            },
            // Inject jQuery import into bootstrap-datepicker locales
            {
                test: /\.js$/,
                use: {
                    loader: "imports-loader",
                    options: {
                        type: 'commonjs',
                        imports: {
                            'moduleName': 'jquery',
                            'name': 'jQuery',
                        }
                    },
                },
                include: path.join(dep, "bootstrap-datepicker", "dist", "locales"),
            },
            // Transpile modern JavaScript for older browsers.
            {
                test: /\.jsx?$/,
                exclude: dep,
                use: {
                    loader: 'babel-loader',
                    options: {
                        cacheDirectory: true,
                        // Use the recommended preset-env
                        presets: [
                            ['@babel/preset-env', {
                                targets: {
                                    browsers: [
                                        'IE 11',
                                        'FF 52',
                                        'Chrome 49',
                                    ],
                                },
                                // Let webpack handle modules
                                modules: false,
                                forceAllTransforms: PROD,
                            }],
                        ],
                        // Import the babel runtime instead of inlining it in
                        // every file.
                        plugins: [
                            ['@babel/plugin-transform-runtime', {
                                helpers: false,
                                regenerator: true,
                                useESModules: true,
                            }],
                        ],
                    },
                },
            },
            // Handle CSS
            {
                test: /\.css$/,
                use: [
                    MiniCssExtractPlugin.loader,
                    "css-loader",
                ],
            },
            // Handle other assets
            {
                test: [
                    /\.(?:gif|ico|jpg|png|svg)$/, // Images
                    /\.(?:eot|otf|ttf|woff|woff2)$/, // Fonts
                    /\.(?:xml)$/, // static XML initial for openSearch
                ],
                exclude: dep,
                use: {
                    loader: 'file-loader',
                    options: {
                        name: "[path][name].[hash].[ext]",
                        publicPath: './',
                        esModule: false,
                    },
                },
            },
        ],
    },
};
<|endoftext|>


<commit_before>const path = require('path');
const webpack = require('webpack');
const TerserPlugin = require('terser-webpack-plugin');
const envKeys = require('./env.config').envKeys;
const BrotliPlugin = require('brotli-webpack-plugin');
const HtmlWebPackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: ['./app.js'],
  mode: 'production',
  devtool: 'source-map',
  resolve: {
    modules: ['node_modules', 'source'],
    extensions: ['.js', '.jsx']
  },
  output: {
    publicPath: '/',
    path: path.resolve(__dirname, 'build'),
    filename: '[name].js'
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: [/node_modules/],
        loader: 'babel-loader'
      },
      {
        test: /\.(png|jp(e*)g|svg)$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 8000,
              name: 'images/[hash]-[name].[ext]'
            }
          }
        ]
      }
    ]
  },
  optimization: {
    minimizer: [
      new TerserPlugin({
        parallel: true,
        sourceMap: true
      })
    ]
  },
  plugins: [
    new webpack.DefinePlugin(envKeys),
    new webpack.optimize.ModuleConcatenationPlugin(),
    new HtmlWebPackPlugin({
      hash: true,
      filename: 'index.html',
      template: './public/index.html',
      favicon: './public/favicon.png'
    }),
    new BrotliPlugin({
      asset: '[path].br[query]',
      test: /\.(js|css|html|svg)$/,
      threshold: 10240,
      minRatio: 0.8,
      deleteOriginalAssets: true
    })
  ]
};
<commit_msg>more changes<commit_after>const path = require('path');
const webpack = require('webpack');
const TerserPlugin = require('terser-webpack-plugin');
const envKeys = require('./env.config').envKeys;
const BrotliPlugin = require('brotli-webpack-plugin');
const HtmlWebPackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: ['./app.js'],
  mode: 'production',
  devtool: 'source-map',
  resolve: {
    modules: ['node_modules', 'source'],
    extensions: ['.js', '.jsx']
  },
  output: {
    publicPath: '/',
    path: path.resolve(__dirname, '../build'),
    filename: '[name].js'
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: [/node_modules/],
        loader: 'babel-loader'
      },
      {
        test: /\.(png|jp(e*)g|svg)$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 8000,
              name: 'images/[hash]-[name].[ext]'
            }
          }
        ]
      }
    ]
  },
  optimization: {
    minimizer: [
      new TerserPlugin({
        parallel: true,
        sourceMap: true
      })
    ]
  },
  plugins: [
    new webpack.DefinePlugin(envKeys),
    new webpack.optimize.ModuleConcatenationPlugin(),
    new HtmlWebPackPlugin({
      hash: true,
      filename: 'index.html',
      template: './public/index.html',
      favicon: './public/favicon.png'
    }),
    new BrotliPlugin({
      asset: '[path].br[query]',
      test: /\.(js|css|html|svg)$/,
      threshold: 10240,
      minRatio: 0.8,
      deleteOriginalAssets: true
    })
  ]
};
<|endoftext|>


<commit_before>'use strict';

// Dependencies
// ------------

var fs = require('fs');
var rimraf = require('rimraf');
var path = require('path');
var request = require("request");
var argv = require('minimist')(process.argv.slice(2));
var chalk = require('chalk');
var cheerio = require('cheerio');
var he = require('he');
var moment = require('moment');

// Helpers
// -------

function printUsage () {
    console.log(
`
Usage:

node runner.js --user=[fotolog_username]

Options:

  --skipcomments

    Don't fetch post comments
`
    );
}

function printLogLevel (str, level) {
    var indentation = Array(level + 1).join('  ');
    console.log(chalk[levelColors[level]](`${indentation}${str}`));
}

function printStatus (status) {
    console.log('  ' + chalk.white(status));
}

/**
 * Returns the numeric part of the URL.
 */
function getUrlNumericPart (url) {
    return parseInt(url.match(/\/(\d+)(?:\/|$)/)[1]);
}

/**
 * Synchronously iterates through all links and incrementally get results,
 * using async callbacks. Returns a Promise which is resolved when all links are
 * iterated.
 */
function iterateLinks (links, index, memory, callback, message) {
    if (message) {
        printLogLevel(`- ${message} - ${index + 1}/${links.length}`, 1);
    }

    return new Promise((resolve, reject) => {
        callback(links[index]).then((results) => {
            memory = memory.concat(results);

            if (index === links.length - 1) {
                resolve(memory);
            } else {
                iterateLinks(links, index + 1, memory, callback, message).then((results) => {
                    resolve(results);
                });
            }
        });
    });
}

// API
// ---

if (argv.help || argv.h) {
    printUsage();
    process.exit(0);
}

if (!argv.user) {
    console.error(chalk.red('You must specify a Fotolog username.\n'));
    printUsage();
    process.exit(1);
}

// Program main execution
// ----------------------

var username = argv.user;
var shouldSkipComments = argv.skipcomments;
var postsPerMosaicPage = 30;
var postsLinks = [];
var posts = [];
var mosaicLink = `http://www.fotolog.com/${username}/mosaic`;
var dirName = `fotolog_${username}_data`;
var localisedDateFormats = {
    it: 'DD MMMM YYYY',
    pt: 'DD MMMM YYYY',
    fr: 'DD MMMM YYYY',
    es: 'DD MMMM YYYY',
    en: 'MMMM DD YYYY'
};
var levelColors = ['green', 'white', 'gray'];

function saveUserFotolog () {
    console.log(chalk.yellow(`Getting data from ${username}...`));

    request(mosaicLink, (error, response, body) => {
        var $ = cheerio.load(body);

        getAllPosts(getMosaicPagesLinks($, username))
            .then(getPostsData)
            .then(savePostsDataToDisk);
    });
}

// Traversing - Gettings Mosaic Pages
// ----------------------------------

/**
 * Look up for the mosaic pagination links on the DOM and returns an array with
 * the link for each page.
 */
function getMosaicPagesLinks ($, username) {
    var $pagination = $('#pagination');
    var lastLink = $pagination.find('a:last-of-type').attr('href');
    var maxOffset;

    printLogLevel('Getting all mosaic pagination links...', 0);

    // There's only one page
    if ($pagination.find('a').length === 1) {
        printLogLevel('- User has only one page of photos', 1);

        maxOffset = 0;
    // The last page is within the first 6 pages, so the last is the link before
    } else if (lastLink === `${mosaicLink}/30`) {
        printLogLevel('- User has up to 6 pages of photos', 1);

        maxOffset = getUrlNumericPart($pagination.find('a:nth-last-of-type(2)').attr('href'));
    // User has a lot of pages, so the last page is the last link
    } else {
        printLogLevel('- User has more than 6 pages of photos', 1);

        maxOffset = getUrlNumericPart(lastLink);
    }

    return buildMosaicPagesLinks(maxOffset);
}

/**
 * Builds an array of all mosaic links, based on the pagination's last offset.
 */
function buildMosaicPagesLinks (maxOffset) {
    var pagesQty = (maxOffset / postsPerMosaicPage) + 1;
    var links = [];

    for (var i = 0; i < pagesQty; i++) {
        let offset = i * postsPerMosaicPage;

        if (offset === 0) {
            links.push(mosaicLink);
        } else {
            links.push(`${mosaicLink}/${offset}`);
        }
    }

    printLogLevel(`- Built links for ${pagesQty} pages`, 1);

    return links;
}

// Traversing - Gettings Posts
// ---------------------------

/**
 * Gets all posts from all mosaic page links. Returns a Promise which is
 * resolved when all posts are retrived.
 */
function getAllPosts (mosaicLinks) {
    printLogLevel('Getting all posts links...', 0);

    return new Promise((resolve, reject) => {
        iterateLinks(mosaicLinks, 0, [], getPostsFromMosaicLink).then(resolve);
    });
}

/**
 * Gets all posts from a single mosaic page link. Returns a Promise which is
 * resolved when all posts are retrived.
 */
function getPostsFromMosaicLink (link) {
    return new Promise((resolve, reject) => {
        request(link, (error, response, body) => {
            var $ = cheerio.load(body);
            var postLinks = [];

            $('#list_photos_mosaic').find('a').each((index, element) => {
                postLinks.push($(element).attr('href'));
            });

            printLogLevel(`- Retrieved ${postLinks.length} post links from ${link}`, 1);

            resolve(postLinks);
        });
    });
}

// Traversing - Gettings photos and data
// -------------------------------------

/**
 * Gets photos and data from all posts. Returns a Promise which is resolved
 * when all data is retrieved.
 */
function getPostsData (postsLinks) {
    printLogLevel(`Retrieving data from ${postsLinks.length} posts...`, 0);

    return new Promise((resolve, reject) => {
        iterateLinks(postsLinks, 0, [], getPostData, 'Retrieving post data').then(resolve);
    });
}

/**
 * Gets a post's main photo and additional data. Returns a Promise which is
 * resolved when the post's data is retrieved.
 */
function getPostData (link) {
    return new Promise((resolve, reject) => {
        request(link, (error, response, body) => {
            var $ = cheerio.load(body);
            var data = {};
            var lang = $('html').attr('lang') || 'en';
            var dateRegex = /[^\s]+\s+[^\s]+\s+[^\s]+\s+\d+\s+(?:Views|Vistas|Vues|Visualizações)/;

            data.id = getUrlNumericPart(link);
            data.imageUrl = $('#flog_img_holder').find('img').attr('src');
            data.description = $('#description_photo').text();
            // Fotolog doesn't give us an easy way to get the date so... yeah.
            moment.locale(lang);
            data.date = moment(data.description.match(dateRegex)[0].split(/\s+/).slice(0, 3).join(' '), localisedDateFormats[lang]);

            if (shouldSkipComments) {
                printLogLevel(`Retrieved post data from ${link}`, 2);

                resolve(data);
                return;
            }

            getPostComments($, data.id).then((comments) => {
                data.comments = comments;

                printLogLevel(`Retrieved post data from ${link}`, 2);

                resolve(data);
            });
        });
    });
}

/**
 * Gets a post's comments. The first 44 are retrieved straight from the DOM.
 * The others are retrieved through POST requests on the comments endpoint.
 * Returns a Promise which is resolved when all comments are retrived.
 */
function getPostComments ($, id) {
    var comments = [];

    $('.flog_img_comments').each((index, element) => {
        // The first element is the login form link :S
        if (index === 0) { return; }

        comments.push({
            avatarImgUrl: $(element).find('.comment_avatar').attr('src'),
            username: $(element).find('p b').text(),
            // this gets the first dd/mm/yyyy occurrence
            date: $(element).children('p').text().match(/\d{2}\/\d{2}\/\d{4}/)[0],
            // since the whole comment block is a single paragraph, we get
            // everything after the first two breaklines
            message: $(element).children('p').html().split('

').slice(1).join('

')
        });
    });

    return new Promise((resolve, reject) => {
        iterateCommentRequests(id, 45, comments).then(resolve);
    });
}

/**
 * Synchronously request comments and incrementally get results,
 * using async callbacks. Returns a Promise which is resolved when the request
 * returns 0 results.
 */
function iterateCommentRequests (id, offset, memory) {
    return new Promise((resolve, reject) => {
        fetchComments(id, offset).then((results) => {
            memory = memory.concat(results);

            if (results.length === 0) {
                resolve(memory);
            } else {
                iterateCommentRequests(id, offset + 45, memory).then((results) => {
                    resolve(results);
                });
            }
        });
    });
}

/**
 * Fetches comments from a given post id and offset. Returns a Promise which
 * is resolved when the comments are fetched.
 */
function fetchComments (id, offset) {
    return new Promise((resolve, reject) => {
        request.post('http://fotolog.com/ajax/load_more_comments', { form: {
            user_name: username,
            identifier: id,
            offset: offset
        } }, (error, response, body) => {
            var response = JSON.parse(body);
            var comments = response.comments;

            if (!response.success || !comments || (!Array.isArray(comments) && Object.keys(comments) === 0)) {
                resolve([]);
                return;
            }

            if (!Array.isArray(comments)) {
                comments = Object.keys(comments).map((index) => {
                    return comments[index];
                });
            }

            resolve(comments.map((comment) => {
                return {
                    avatarImgUrl : comment.avatar,
                    username: comment.poster_user_name,
                    date: comment.posted.split(/\s+/)[1],
                    message: comment.message
                }
            }));
        });
    });
}

// Saving data to the disk
// -----------------------

function savePostsDataToDisk (postsData) {
    printLogLevel(`Saving all data from ${username}`, 0);

    rimraf.sync(dirName);

    printLogLevel(`- Removed any previous data`, 1);

    fs.mkdirSync(dirName);

    printLogLevel(`- Created output directory`, 1);

    return new Promise((resolve, reject) => {
        iterateLinks(postsData, 0, [], savePostDataToDisk, 'Saving post data').then(resolve);
    });
}

function savePostDataToDisk (postData) {
    return new Promise((resolve, reject) => {
        var postYear = postData.date.format('YYYY');
        var postMonth = postData.date.format('MM');
        var postDay = postData.date.format('DD');
        var filePath = `${dirName}/${postYear}/${postMonth}`;
        var filePrefix = `${postYear}${postMonth}${postDay}_`;

        try {
            fs.mkdirSync(`${dirName}/${postYear}`);
        } catch(e) {}

        try {
            fs.mkdirSync(`${dirName}/${postYear}/${postMonth}`);
        } catch(e) {}

        fs.writeFileSync(`${filePath}/${filePrefix}${postData.id}.txt`,
            he.decode(postData.description) +
            (shouldSkipComments ?
                '' :
                '\n\n' +
                '========================================\n' +
                '                COMMENTS\n' +
                '========================================\n\n' +
                he.decode(postData.comments.map((comment) => {
                    return `${comment.username} on ${comment.date}:\n\n${comment.message}`;
                }).join('\n\n-------------------\n\n')))
        );

        request(postData.imageUrl).pipe(fs.createWriteStream(`${filePath}/${filePrefix}${postData.id}.jpg`)).on('close', resolve);

        printLogLevel(`Saved photo and post data from post ${postData.id}`, 2);
        printLogLevel(`Post date: ${postDay}/${postMonth}/${postYear}`, 2);
        printLogLevel(`Comments: ${postData.comments.length}`, 2);
    });
}

saveUserFotolog();<commit_msg>Print 'done' message, and removed unused function<commit_after>'use strict';

// Dependencies
// ------------

var fs = require('fs');
var rimraf = require('rimraf');
var path = require('path');
var request = require("request");
var argv = require('minimist')(process.argv.slice(2));
var chalk = require('chalk');
var cheerio = require('cheerio');
var he = require('he');
var moment = require('moment');

// Helpers
// -------

function printUsage () {
    console.log(
`
Usage:

node runner.js --user=[fotolog_username]

Options:

  --skipcomments

    Don't fetch post comments
`
    );
}

function printLogLevel (str, level) {
    var indentation = Array(level + 1).join('  ');
    console.log(chalk[levelColors[level]](`${indentation}${str}`));
}

/**
 * Returns the numeric part of the URL.
 */
function getUrlNumericPart (url) {
    return parseInt(url.match(/\/(\d+)(?:\/|$)/)[1]);
}

/**
 * Synchronously iterates through all links and incrementally get results,
 * using async callbacks. Returns a Promise which is resolved when all links are
 * iterated.
 */
function iterateLinks (links, index, memory, callback, message) {
    if (message) {
        printLogLevel(`- ${message} - ${index + 1}/${links.length}`, 1);
    }

    return new Promise((resolve, reject) => {
        callback(links[index]).then((results) => {
            memory = memory.concat(results);

            if (index === links.length - 1) {
                resolve(memory);
            } else {
                iterateLinks(links, index + 1, memory, callback, message).then((results) => {
                    resolve(results);
                });
            }
        });
    });
}

// API
// ---

if (argv.help || argv.h) {
    printUsage();
    process.exit(0);
}

if (!argv.user) {
    console.error(chalk.red('You must specify a Fotolog username.\n'));
    printUsage();
    process.exit(1);
}

// Program main execution
// ----------------------

var username = argv.user;
var shouldSkipComments = argv.skipcomments;
var postsPerMosaicPage = 30;
var postsLinks = [];
var posts = [];
var mosaicLink = `http://www.fotolog.com/${username}/mosaic`;
var dirName = `fotolog_${username}_data`;
var localisedDateFormats = {
    it: 'DD MMMM YYYY',
    pt: 'DD MMMM YYYY',
    fr: 'DD MMMM YYYY',
    es: 'DD MMMM YYYY',
    en: 'MMMM DD YYYY'
};
var levelColors = ['green', 'white', 'gray'];

function saveUserFotolog () {
    console.log(chalk.yellow(`Getting data from ${username}...`));

    request(mosaicLink, (error, response, body) => {
        var $ = cheerio.load(body);

        getAllPosts(getMosaicPagesLinks($, username))
            .then(getPostsData)
            .then(savePostsDataToDisk)
            .then((postsData) => {
                printLogLevel(`Done! Saved ${postsData.length} post(s) to the ${dirName} folder.`, 0);
            });
    });
}

// Traversing - Gettings Mosaic Pages
// ----------------------------------

/**
 * Look up for the mosaic pagination links on the DOM and returns an array with
 * the link for each page.
 */
function getMosaicPagesLinks ($, username) {
    var $pagination = $('#pagination');
    var lastLink = $pagination.find('a:last-of-type').attr('href');
    var maxOffset;

    printLogLevel('Getting all mosaic pagination links...', 0);

    // There's only one page
    if ($pagination.find('a').length === 1) {
        printLogLevel('- User has only one page of photos', 1);

        maxOffset = 0;
    // The last page is within the first 6 pages, so the last is the link before
    } else if (lastLink === `${mosaicLink}/30`) {
        printLogLevel('- User has up to 6 pages of photos', 1);

        maxOffset = getUrlNumericPart($pagination.find('a:nth-last-of-type(2)').attr('href'));
    // User has a lot of pages, so the last page is the last link
    } else {
        printLogLevel('- User has more than 6 pages of photos', 1);

        maxOffset = getUrlNumericPart(lastLink);
    }

    return buildMosaicPagesLinks(maxOffset);
}

/**
 * Builds an array of all mosaic links, based on the pagination's last offset.
 */
function buildMosaicPagesLinks (maxOffset) {
    var pagesQty = (maxOffset / postsPerMosaicPage) + 1;
    var links = [];

    for (var i = 0; i < pagesQty; i++) {
        let offset = i * postsPerMosaicPage;

        if (offset === 0) {
            links.push(mosaicLink);
        } else {
            links.push(`${mosaicLink}/${offset}`);
        }
    }

    printLogLevel(`- Built links for ${pagesQty} pages`, 1);

    return links;
}

// Traversing - Gettings Posts
// ---------------------------

/**
 * Gets all posts from all mosaic page links. Returns a Promise which is
 * resolved when all posts are retrived.
 */
function getAllPosts (mosaicLinks) {
    printLogLevel('Getting all posts links...', 0);

    return new Promise((resolve, reject) => {
        iterateLinks(mosaicLinks, 0, [], getPostsFromMosaicLink).then(resolve);
    });
}

/**
 * Gets all posts from a single mosaic page link. Returns a Promise which is
 * resolved when all posts are retrived.
 */
function getPostsFromMosaicLink (link) {
    return new Promise((resolve, reject) => {
        request(link, (error, response, body) => {
            var $ = cheerio.load(body);
            var postLinks = [];

            $('#list_photos_mosaic').find('a').each((index, element) => {
                postLinks.push($(element).attr('href'));
            });

            printLogLevel(`- Retrieved ${postLinks.length} post links from ${link}`, 1);

            resolve(postLinks);
        });
    });
}

// Traversing - Gettings photos and data
// -------------------------------------

/**
 * Gets photos and data from all posts. Returns a Promise which is resolved
 * when all data is retrieved.
 */
function getPostsData (postsLinks) {
    printLogLevel(`Retrieving data from ${postsLinks.length} posts...`, 0);

    return new Promise((resolve, reject) => {
        iterateLinks(postsLinks, 0, [], getPostData, 'Retrieving post data').then(resolve);
    });
}

/**
 * Gets a post's main photo and additional data. Returns a Promise which is
 * resolved when the post's data is retrieved.
 */
function getPostData (link) {
    return new Promise((resolve, reject) => {
        request(link, (error, response, body) => {
            var $ = cheerio.load(body);
            var data = {};
            var lang = $('html').attr('lang') || 'en';
            var dateRegex = /[^\s]+\s+[^\s]+\s+[^\s]+\s+\d+\s+(?:Views|Vistas|Vues|Visualizações)/;

            data.id = getUrlNumericPart(link);
            data.imageUrl = $('#flog_img_holder').find('img').attr('src');
            data.description = $('#description_photo').text();
            // Fotolog doesn't give us an easy way to get the date so... yeah.
            moment.locale(lang);
            data.date = moment(data.description.match(dateRegex)[0].split(/\s+/).slice(0, 3).join(' '), localisedDateFormats[lang]);

            if (shouldSkipComments) {
                printLogLevel(`Retrieved post data from ${link}`, 2);

                resolve(data);
                return;
            }

            getPostComments($, data.id).then((comments) => {
                data.comments = comments;

                printLogLevel(`Retrieved post data from ${link}`, 2);

                resolve(data);
            });
        });
    });
}

/**
 * Gets a post's comments. The first 44 are retrieved straight from the DOM.
 * The others are retrieved through POST requests on the comments endpoint.
 * Returns a Promise which is resolved when all comments are retrived.
 */
function getPostComments ($, id) {
    var comments = [];

    $('.flog_img_comments').each((index, element) => {
        // The first element is the login form link :S
        if (index === 0) { return; }

        comments.push({
            avatarImgUrl: $(element).find('.comment_avatar').attr('src'),
            username: $(element).find('p b').text(),
            // this gets the first dd/mm/yyyy occurrence
            date: $(element).children('p').text().match(/\d{2}\/\d{2}\/\d{4}/)[0],
            // since the whole comment block is a single paragraph, we get
            // everything after the first two breaklines
            message: $(element).children('p').html().split('

').slice(1).join('

')
        });
    });

    return new Promise((resolve, reject) => {
        iterateCommentRequests(id, 45, comments).then(resolve);
    });
}

/**
 * Synchronously request comments and incrementally get results,
 * using async callbacks. Returns a Promise which is resolved when the request
 * returns 0 results.
 */
function iterateCommentRequests (id, offset, memory) {
    return new Promise((resolve, reject) => {
        fetchComments(id, offset).then((results) => {
            memory = memory.concat(results);

            if (results.length === 0) {
                resolve(memory);
            } else {
                iterateCommentRequests(id, offset + 45, memory).then((results) => {
                    resolve(results);
                });
            }
        });
    });
}

/**
 * Fetches comments from a given post id and offset. Returns a Promise which
 * is resolved when the comments are fetched.
 */
function fetchComments (id, offset) {
    return new Promise((resolve, reject) => {
        request.post('http://fotolog.com/ajax/load_more_comments', { form: {
            user_name: username,
            identifier: id,
            offset: offset
        } }, (error, response, body) => {
            var response = JSON.parse(body);
            var comments = response.comments;

            if (!response.success || !comments || (!Array.isArray(comments) && Object.keys(comments) === 0)) {
                resolve([]);
                return;
            }

            if (!Array.isArray(comments)) {
                comments = Object.keys(comments).map((index) => {
                    return comments[index];
                });
            }

            resolve(comments.map((comment) => {
                return {
                    avatarImgUrl : comment.avatar,
                    username: comment.poster_user_name,
                    date: comment.posted.split(/\s+/)[1],
                    message: comment.message
                }
            }));
        });
    });
}

// Saving data to the disk
// -----------------------

function savePostsDataToDisk (postsData) {
    printLogLevel(`Saving all data from ${username}`, 0);

    rimraf.sync(dirName);

    printLogLevel(`- Removed any previous data`, 1);

    fs.mkdirSync(dirName);

    printLogLevel(`- Created output directory`, 1);

    return new Promise((resolve, reject) => {
        iterateLinks(postsData, 0, [], savePostDataToDisk, 'Saving post data').then(resolve);
    });
}

function savePostDataToDisk (postData) {
    return new Promise((resolve, reject) => {
        var postYear = postData.date.format('YYYY');
        var postMonth = postData.date.format('MM');
        var postDay = postData.date.format('DD');
        var filePath = `${dirName}/${postYear}/${postMonth}`;
        var filePrefix = `${postYear}${postMonth}${postDay}_`;

        try {
            fs.mkdirSync(`${dirName}/${postYear}`);
        } catch(e) {}

        try {
            fs.mkdirSync(`${dirName}/${postYear}/${postMonth}`);
        } catch(e) {}

        fs.writeFileSync(`${filePath}/${filePrefix}${postData.id}.txt`,
            he.decode(postData.description) +
            (shouldSkipComments ?
                '' :
                '\n\n' +
                '========================================\n' +
                '                COMMENTS\n' +
                '========================================\n\n' +
                he.decode(postData.comments.map((comment) => {
                    return `${comment.username} on ${comment.date}:\n\n${comment.message}`;
                }).join('\n\n-------------------\n\n')))
        );

        request(postData.imageUrl).pipe(fs.createWriteStream(`${filePath}/${filePrefix}${postData.id}.jpg`)).on('close', resolve);

        printLogLevel(`Saved photo and post data from post ${postData.id}`, 2);
        printLogLevel(`Post date: ${postDay}/${postMonth}/${postYear}`, 2);
        printLogLevel(`Comments: ${postData.comments.length}`, 2);
    });
}

saveUserFotolog();<|endoftext|>


<commit_before>var mapzen = mapzen || {};
mapzen.whosonfirst = mapzen.whosonfirst || {};
mapzen.whosonfirst.boundaryissues = mapzen.whosonfirst.boundaryissues || {};

// for the time being this assumes jQuery is present
// that decision may change some day but not today
// (20160121/thisisaaronland)

mapzen.whosonfirst.boundaryissues.notifications = (function() {

	var socket;
	var notification_icon = 'https://avatars1.githubusercontent.com/u/13841686?v=3&s=200';

	var self = {
		setup_websocket: function() {
			//var url = mapzen.whosonfirst.boundaryissues.utils.abs_root_urlify('/ws/');
			var url = 'https://whosonfirst.mapzen.com/bi/ws/';
			url = url.replace(/^http:/, 'ws:');
			url = url.replace(/^https:/, 'wss:');
			socket = new WebSocket(url);
			socket.onmessage = self.handle_message;
			$(window).on('beforeunload', function() {
				socket.close();
			});
		},

		handle_message: function(rsp) {
			var data = JSON.parse(rsp['data']);
			if (data.details && data.details.user_ids) {

				// This is not a particularly safe or private
				// way to handle user-specific notifications,
				// but it will do for our current needs.
				// (20160603/dphiffer)

				var user_id = $(document.body).data('user-id');
				user_id = parseInt(user_id);

				if (! user_id) {
					// Not signed in
					return;
				}

				if (data.details.user_ids.indexOf(user_id) == -1) {
					// Not for us
					return;
				}
			}

			if (! data.title ||
			    ! data.body) {
				mapzen.whosonfirst.log.error('Invalid notification: ' + JSON.stringify(data));
			} else {
				self.request_notification(data);

				// Log it!
				var log_msg = data.title + ' / ' + data.body;
				if (data.details) {
					log_msg += ' / ' + JSON.stringify(data.details);
				}
				mapzen.whosonfirst.log.info(log_msg);
			}
		},

		request_notification: function(data) {
			if (! ("Notification" in window)) {
				// Not supported
				return;
			} else if (Notification.permission == "granted") {
				self.send_notification(data);
			} else if (Notification.permission != "denied") {
				Notification.requestPermission(function(permission) {
					if (permission == "granted") {
						self.send_notification(data);
					}
				});
			}
		},

		send_notification: function(data) {
			var n = new Notification(data.title, {
				body: data.body,
				icon: notification_icon
			});
		}
	};

	$(document).ready(function() {
		self.setup_websocket();
	});

	return self;

})();
<commit_msg>:bell: Put the endpoint back where it was<commit_after>var mapzen = mapzen || {};
mapzen.whosonfirst = mapzen.whosonfirst || {};
mapzen.whosonfirst.boundaryissues = mapzen.whosonfirst.boundaryissues || {};

// for the time being this assumes jQuery is present
// that decision may change some day but not today
// (20160121/thisisaaronland)

mapzen.whosonfirst.boundaryissues.notifications = (function() {

	var socket;
	var notification_icon = 'https://avatars1.githubusercontent.com/u/13841686?v=3&s=200';

	var self = {
		setup_websocket: function() {
			var url = mapzen.whosonfirst.boundaryissues.utils.abs_root_urlify('/ws/');
			url = url.replace(/^http:/, 'ws:');
			url = url.replace(/^https:/, 'wss:');
			socket = new WebSocket(url);
			socket.onmessage = self.handle_message;
			$(window).on('beforeunload', function() {
				socket.close();
			});
		},

		handle_message: function(rsp) {
			var data = JSON.parse(rsp['data']);
			if (data.details && data.details.user_ids) {

				// This is not a particularly safe or private
				// way to handle user-specific notifications,
				// but it will do for our current needs.
				// (20160603/dphiffer)

				var user_id = $(document.body).data('user-id');
				user_id = parseInt(user_id);

				if (! user_id) {
					// Not signed in
					return;
				}

				if (data.details.user_ids.indexOf(user_id) == -1) {
					// Not for us
					return;
				}
			}

			if (! data.title ||
			    ! data.body) {
				mapzen.whosonfirst.log.error('Invalid notification: ' + JSON.stringify(data));
			} else {
				self.request_notification(data);

				// Log it!
				var log_msg = data.title + ' / ' + data.body;
				if (data.details) {
					log_msg += ' / ' + JSON.stringify(data.details);
				}
				mapzen.whosonfirst.log.info(log_msg);
			}
		},

		request_notification: function(data) {
			if (! ("Notification" in window)) {
				// Not supported
				return;
			} else if (Notification.permission == "granted") {
				self.send_notification(data);
			} else if (Notification.permission != "denied") {
				Notification.requestPermission(function(permission) {
					if (permission == "granted") {
						self.send_notification(data);
					}
				});
			}
		},

		send_notification: function(data) {
			var n = new Notification(data.title, {
				body: data.body,
				icon: notification_icon
			});
		}
	};

	$(document).ready(function() {
		self.setup_websocket();
	});

	return self;

})();
<|endoftext|>


<commit_before>AUTH_CONSTANTS = {
    loginFormId : 'login-form',
    changePwdFormId : 'changepassword-form',
    changePwdPopupFlag : 'chpwd',
    authLoginPath : '/auth/login',
    authChangePwdPath : '/auth/changePassword',
    chpwdContentPath : '/content/changePassword',
    SUCCESS_CODE : 'success',
    ERROR_CODE : 'error',
    msgContaineId : 'auth-msg-container',
    emptyCredentials : "Username / Password can't be left empty",
    invalidUserName : "Only alphbates and '_' are allowed in User Name",
    emptyPasswords : "Password fields can't be left empty"
};

var cssSelectors = {
    popupBg : '.popup-opacity-background',
    chpwdContainer : '#chpwd-container',
    chpwdLink : '.change-password-link'
};

addEvent(window, 'load', function() {
    var forms = document.getElementsByClassName('auth-form');
    for ( var index = 0; index < forms.length; index++) {
        var form = forms[index];
        for ( var i = 0; i < form.length; i++) {
            if (form[i].nodeName === "INPUT") {
                var el = form[i];
                addEvent(el, 'keypress', function(e) {
                    if (e.keyCode == 13) {
                        if (form.id == AUTH_CONSTANTS.loginFormId) {
                            doLogin(form.id);
                        } else if (form.id == AUTH_CONSTANTS.changePwdFormId) {
                            changePassword(form.id);
                        }
                    }
                });
            }
        }
    }
});

function doLogin(formId) {
    var formElem = document.getElementById(formId);
    var formInputFields = [ formElem.username, formElem.password ];
    if (validate(formElem, formInputFields, AUTH_CONSTANTS.emptyCredentials)) {
        $('#do-login').addClass('disabled');
        $('#do-login').val(' Signing In... ');
        var formData = populateLoginFormData(formElem);
        AJAX.onreadystatechange = function() {
            if (AJAX.readyState == 4 && AJAX.status == 200) {
                var response = JSON.parse(AJAX.responseText);
                var msgContainerId = AUTH_CONSTANTS.msgContaineId;
                var msgContainer = document.getElementById(msgContainerId);
                if (response.code == AUTH_CONSTANTS.SUCCESS_CODE) {
                    msgContainer.className = "success-msg-div";
                    document.getElementById(formId).reset();
                    window.location.reload();
                } else if (response.code == AUTH_CONSTANTS.ERROR_CODE) {
                    msgContainer.className = "error-msg-div";
                }
                msgContainer.innerHTML = response.msg;
                msgContainer.style.display = "block";
                $('#do-login').removeClass('disabled');
                $('#do-login').val(' Sign In ');
            }
        };
        AJAX.open("POST", AUTH_CONSTANTS.authLoginPath);
        AJAX.setRequestHeader('X_REQUESTED_WITH', 'XMLHttpRequest');
        AJAX.send(formData);
    }
}

function changePassword(formId) {
    var formElem = document.getElementById(formId);
    var formInputFields = [ formElem.currentpassword, formElem.newpassword ];
    if (validate(formElem, formInputFields, AUTH_CONSTANTS.emptyPasswords)) {
        var formData = populateChpwdFormData(formElem);
        AJAX.onreadystatechange = function() {
            if (AJAX.readyState == 4 && AJAX.status == 200) {
                var response = JSON.parse(AJAX.responseText);
                var msgContainerId = AUTH_CONSTANTS.msgContaineId;
                var msgContainer = document.getElementById(msgContainerId);
                if (response.code == AUTH_CONSTANTS.SUCCESS_CODE) {
                    msgContainer.className = "success-msg-div";
                    document.getElementById(formId).reset();
                    setTimeout(function() {
                        closePopup();
                    }, 2000);
                } else if (response.code == AUTH_CONSTANTS.ERROR_CODE) {
                    msgContainer.className = "error-msg-div";
                }
                msgContainer.innerHTML = response.msg;
                msgContainer.style.display = "block";
            }
        };
        AJAX.open("POST", AUTH_CONSTANTS.authChangePwdPath);
        AJAX.setRequestHeader('X_REQUESTED_WITH', 'XMLHttpRequest');
        AJAX.send(formData);
    }
}

function populateLoginFormData(formElem) {
    var formData = new FormData();
    formData.append('username', formElem.username.value);
    formData.append('password', formElem.password.value);
    formData.append(APP_CONSTANTS.CSRF_TOKEN_NAME, formElem[APP_CONSTANTS.CSRF_TOKEN_NAME].value);
    formData.append('remember', formElem.remember.checked);
    formData.append('login-action-name', document
            .getElementById('login-action-name').value);
    return formData;
}

function populateChpwdFormData(formElem) {
    var formData = new FormData();
    formData.append('currentpassword', formElem.currentpassword.value);
    formData.append('newpassword', formElem.newpassword.value);
    formData.append(APP_CONSTANTS.CSRF_TOKEN_NAME,
            formElem[APP_CONSTANTS.CSRF_TOKEN_NAME].value);
    formData.append('chpwd-action-name', document
            .getElementById('chpwd-action-name').value);
    return formData;
}

function validate(formElem, credential, errMsg) {
    var isError = false;
    var errorMsgElem = document.getElementById('auth-msg-container');
    resetErrors(credential);
    for (index in credential) {
        if (credential[index].value == 0) {
            errorMsgElem.innerHTML = errMsg;
            errorMsgElem.className = "error-msg-div";
            errorMsgElem.style.display = "block";
            credential[index].className += " error-field";
            isError = true;
        }
    }
    return (!isError);
}

function resetErrors(credential) {
    for (index in credential) {
        removeClass(credential[index], "error-field");
    }
}

$('#do-login').click(function() {
    doLogin(AUTH_CONSTANTS.loginFormId);
});

$(cssSelectors.chpwdLink).click(
        function() {
            attachPopupEvents(cssSelectors.popupBg,
                    cssSelectors.chpwdContainer,
                    AUTH_CONSTANTS.changePwdPopupFlag, getChangePasswordUI);
            $(APP_CONSTANTS.cssSelectors.accountDD).slideToggle();
        });

function getChangePasswordUI() {
    formData = new FormData();
    formData.append('time', 100000);
    AJAX.onreadystatechange = function() {
        if (AJAX.readyState == 4 && AJAX.status == 200) {
            var response = JSON.parse(AJAX.responseText);
            if (response.code == AUTH_CONSTANTS.SUCCESS_CODE) {
                $(cssSelectors.chpwdContainer).html(response.detail);
            } else if (response.code == AUTH_CONSTANTS.ERROR_CODE) {
                // msgContainer.className = "error-msg-div";
            }
        }
    };
    AJAX.open("GET", AUTH_CONSTANTS.chpwdContentPath);
    AJAX.setRequestHeader('X_REQUESTED_WITH', 'XMLHttpRequest');
    AJAX.send(formData);
}<commit_msg>Changes in login success ajax response<commit_after>AUTH_CONSTANTS = {
    loginFormId : 'login-form',
    changePwdFormId : 'changepassword-form',
    changePwdPopupFlag : 'chpwd',
    authLoginPath : '/auth/login',
    authChangePwdPath : '/auth/changePassword',
    chpwdContentPath : '/content/changePassword',
    SUCCESS_CODE : 'success',
    ERROR_CODE : 'error',
    msgContaineId : 'auth-msg-container',
    emptyCredentials : "Username / Password can't be left empty",
    invalidUserName : "Only alphbates and '_' are allowed in User Name",
    emptyPasswords : "Password fields can't be left empty"
};

var cssSelectors = {
    popupBg : '.popup-opacity-background',
    chpwdContainer : '#chpwd-container',
    chpwdLink : '.change-password-link'
};

addEvent(window, 'load', function() {
    var forms = document.getElementsByClassName('auth-form');
    for ( var index = 0; index < forms.length; index++) {
        var form = forms[index];
        for ( var i = 0; i < form.length; i++) {
            if (form[i].nodeName === "INPUT") {
                var el = form[i];
                addEvent(el, 'keypress', function(e) {
                    if (e.keyCode == 13) {
                        if (form.id == AUTH_CONSTANTS.loginFormId) {
                            doLogin(form.id);
                        } else if (form.id == AUTH_CONSTANTS.changePwdFormId) {
                            changePassword(form.id);
                        }
                    }
                });
            }
        }
    }
});

function doLogin(formId) {
    var formElem = document.getElementById(formId);
    var formInputFields = [ formElem.username, formElem.password ];
    if (validate(formElem, formInputFields, AUTH_CONSTANTS.emptyCredentials)) {
        $('#do-login').addClass('disabled');
        $('#do-login').val(' Signing In... ');
        var formData = populateLoginFormData(formElem);
        AJAX.onreadystatechange = function() {
            if (AJAX.readyState == 4 && AJAX.status == 200) {
                var response = JSON.parse(AJAX.responseText);
                var msgContainerId = AUTH_CONSTANTS.msgContaineId;
                var msgContainer = document.getElementById(msgContainerId);
                if (response.code == AUTH_CONSTANTS.SUCCESS_CODE) {
                    window.location.reload();
                } else if (response.code == AUTH_CONSTANTS.ERROR_CODE) {
                    msgContainer.className = "error-msg-div";
                    msgContainer.innerHTML = response.msg;
                    msgContainer.style.display = "block";
                    $('#do-login').removeClass('disabled');
                    $('#do-login').val(' Sign In ');
                }
            }
        };
        AJAX.open("POST", AUTH_CONSTANTS.authLoginPath);
        AJAX.setRequestHeader('X_REQUESTED_WITH', 'XMLHttpRequest');
        AJAX.send(formData);
    }
}

function changePassword(formId) {
    var formElem = document.getElementById(formId);
    var formInputFields = [ formElem.currentpassword, formElem.newpassword ];
    if (validate(formElem, formInputFields, AUTH_CONSTANTS.emptyPasswords)) {
        var formData = populateChpwdFormData(formElem);
        AJAX.onreadystatechange = function() {
            if (AJAX.readyState == 4 && AJAX.status == 200) {
                var response = JSON.parse(AJAX.responseText);
                var msgContainerId = AUTH_CONSTANTS.msgContaineId;
                var msgContainer = document.getElementById(msgContainerId);
                if (response.code == AUTH_CONSTANTS.SUCCESS_CODE) {
                    msgContainer.className = "success-msg-div";
                    document.getElementById(formId).reset();
                    setTimeout(function() {
                        closePopup();
                    }, 2000);
                } else if (response.code == AUTH_CONSTANTS.ERROR_CODE) {
                    msgContainer.className = "error-msg-div";
                }
                msgContainer.innerHTML = response.msg;
                msgContainer.style.display = "block";
            }
        };
        AJAX.open("POST", AUTH_CONSTANTS.authChangePwdPath);
        AJAX.setRequestHeader('X_REQUESTED_WITH', 'XMLHttpRequest');
        AJAX.send(formData);
    }
}

function populateLoginFormData(formElem) {
    var formData = new FormData();
    formData.append('username', formElem.username.value);
    formData.append('password', formElem.password.value);
    formData.append(APP_CONSTANTS.CSRF_TOKEN_NAME, formElem[APP_CONSTANTS.CSRF_TOKEN_NAME].value);
    formData.append('remember', formElem.remember.checked);
    formData.append('login-action-name', document
            .getElementById('login-action-name').value);
    return formData;
}

function populateChpwdFormData(formElem) {
    var formData = new FormData();
    formData.append('currentpassword', formElem.currentpassword.value);
    formData.append('newpassword', formElem.newpassword.value);
    formData.append(APP_CONSTANTS.CSRF_TOKEN_NAME,
            formElem[APP_CONSTANTS.CSRF_TOKEN_NAME].value);
    formData.append('chpwd-action-name', document
            .getElementById('chpwd-action-name').value);
    return formData;
}

function validate(formElem, credential, errMsg) {
    var isError = false;
    var errorMsgElem = document.getElementById('auth-msg-container');
    resetErrors(credential);
    for (index in credential) {
        if (credential[index].value == 0) {
            errorMsgElem.innerHTML = errMsg;
            errorMsgElem.className = "error-msg-div";
            errorMsgElem.style.display = "block";
            credential[index].className += " error-field";
            isError = true;
        }
    }
    return (!isError);
}

function resetErrors(credential) {
    for (index in credential) {
        removeClass(credential[index], "error-field");
    }
}

$('#do-login').click(function() {
    doLogin(AUTH_CONSTANTS.loginFormId);
});

$(cssSelectors.chpwdLink).click(
        function() {
            attachPopupEvents(cssSelectors.popupBg,
                    cssSelectors.chpwdContainer,
                    AUTH_CONSTANTS.changePwdPopupFlag, getChangePasswordUI);
            $(APP_CONSTANTS.cssSelectors.accountDD).slideToggle();
        });

function getChangePasswordUI() {
    formData = new FormData();
    formData.append('time', 100000);
    AJAX.onreadystatechange = function() {
        if (AJAX.readyState == 4 && AJAX.status == 200) {
            var response = JSON.parse(AJAX.responseText);
            if (response.code == AUTH_CONSTANTS.SUCCESS_CODE) {
                $(cssSelectors.chpwdContainer).html(response.detail);
            } else if (response.code == AUTH_CONSTANTS.ERROR_CODE) {
                // msgContainer.className = "error-msg-div";
            }
        }
    };
    AJAX.open("GET", AUTH_CONSTANTS.chpwdContentPath);
    AJAX.setRequestHeader('X_REQUESTED_WITH', 'XMLHttpRequest');
    AJAX.send(formData);
}<|endoftext|>


<commit_before>/**
 * NOTE: FilterCollection is considered alpha/experimental and although this
 * most likely won't happen, it may be subject to substantial API changes. Use/
 * depend on at your own risk!
 *
 * It's also completely undocumented...good luck!
 * -----------------------------------------------------------------------------
 *
 * Composer.js is an MVC framework for creating and organizing javascript
 * applications. For documentation, please visit:
 *
 *     http://lyonbros.github.com/composer.js/
 *
 * -----------------------------------------------------------------------------
 *
 * Copyright (c) 2011, Lyon Bros Enterprises, LLC. (http://www.lyonbros.com)
 *
 * Licensed under The MIT License.
 * Redistributions of files must retain the above copyright notice.
 */
(function() {
	/**
	 * Collection that exists solely to be a "materialized view" of another
	 * "master" collection. Whenever items are added/removed from the master
	 * collection, the changes are filtered and applied to this collection as well.
	 * This is useful for keeping many collections in sync with one master list
	 * without having to manually update them all.
	 */
	var FilterCollection	=	new Class({
		Extends: Composer.Collection,

		/**
		 * Track this object's type. Useful for debugging, mainly
		 */
		__composer_type: 'filtercollection',

		master: null,
		filter: null,
		transform: null,
		limit: false,

		_do_match_action: null,

		forward_all_events: false,
		refresh_on_change: true,	// performance hit, but needed for backward compat
		sort_event: false,			// if true, fires a 'sort' event instead of 'reset' when sorting

		initialize: function(master, options)
		{
			options || (options = {});

			Object.each(Object.clone(options), function(v, k) {
				if(typeof(v) == 'function') v = v.bind(this);
				this[k]	=	v;
			}, this);

			// assign the unique app id
			this._cid	=	Composer.cid();

			this.master	=	master;

			if(!this.master) return false;
			if(!this.filter) return false;

			this.attach(options);
			if(!options.skip_initial_sync) this.refresh();
		},

		attach: function()
		{
			if(!this._do_match_action) this._do_match_action = this.match_action.bind(this);
			this.master.bind('all', this._do_match_action, 'filtercollection:'+this.cid()+':all');
			this.bind('reset', function(options) {
				options || (options = {});
				if(options.has_reload) return false;
				this.refresh(options);
			}.bind(this), 'filtercollection:reset');
		},

		detach: function()
		{
			this.master.unbind('all', 'filtercollection:'+this.cid()+':all');
			this.unbind('reset', 'filtercollection:reset');
		},

		match_action: function(event, model)
		{
			switch(event)
			{
			case 'add':
				this.add_event(model, {from_event: true}); break;
			case 'reset':
				this.refresh(); break;
			case 'clear':
				this.clear(); break;
			case 'remove':
				this.remove_event(model, {from_event: true}); break;
			case 'change':
				this.change_event(model); break;
			case 'sort':
				this.refresh(); break;
			default:
				this.forward_event.apply(this, arguments); break;
			}
		},

		refresh: function(options)
		{
			options || (options = {});

			if(options.diff_events)
			{
				var old_models	=	this._models;
			}
			this._models	=	this.master._models.filter(function(model) {
				return this.filter(model, this);
			}.bind(this));
			this.sort({silent: true});
			if(this.limit) this._models.splice(this.limit);
			if(options.diff_events)
			{
				var arrdiff	=	function(arr1, arr2) { return arr1.filter(function(el) { return !arr2.contains(el); }); };

				arrdiff(old_models, this._models).each(function(model) {
					this.fire_event('remove', options, model);
				}, this);

				arrdiff(this._models, old_models).each(function(model) {
					this.fire_event('add', options, model);
				}, this);
			}
			this.fire_event('reset', options, {has_reload: true});
		},

		change_event: function(model, options)
		{
			options || (options = {});

			// see if this model even belongs to this collection
			if(model && !this.filter(model, this)) return false;

			// track the current number of items and reloda the data
			var num_items	=	this._models.length;

			if(this.refresh_on_change)
			{
				// the brute force option (re-sort everything, re-filter everything)
				// VERY expensive
				this.refresh({silent: true});
			}
			else
			{
				// a more tactful approach
				var cur_index = this._models.indexOf(model);
				var new_index = this.sort_index(model);

				if(cur_index == -1 && this.filter(model, this))
				{
					// welcome to the team!
					this.add(model, options);
				}
				else if(cur_index > -1 && !this.filter(model, this))
				{
					// we feel that your interests no longer align with the team's
					// ...we're going to ahve to let you go.
					//
					// You BASTARDS I've poured my LIFE into this collection!!
					//
					// Yes and we're thankful for your hard work, but feel it's
					// time to move on. Your replacement is a potted plant (come
					// to think of it, so is your severance). Think of this as a
					// new beginning! Now get out of my office.
					this.remove(model, options);
				}
				else if(cur_index != new_index)
				{
					// sort order changed
					if(this.sort_event)
					{
						this.sort(Object.merge({}, options, {silent: true}));
						this.fire_event('sort', options);
					}
					else
					{
						this.sort(options);
					}
				}
			}

			// do nothing if no change
			if(this._models.length == num_items)
			{
				this.fire_event('change', options);
			}
			else
			{
				this.fire_event('reset', options);
			}
		},

		set_filter: function(fn, options)
		{
			if(!fn) return false;
			options || (options = {refresh: true});
			this.filter	=	fn.bind(this);
			if(!options.refresh) this.refresh();
		},

		add: function(data, options)
		{
			if (data instanceof Array)
			{
				return Object.each(data, function(model) { this.add(model, options); }, this);
			}

			options || (options = {});
			if(typeof(options.transform) == 'undefined') options.transform = true;

			// if we are passing raw data, create a new model from data
			var model		=	data.__composer_type == 'model' ? data : new this.master.model(data, options);

			if(this.transform && options.transform)
			{
				model	=	this.transform.call(this, model, 'add');
			}

			// model doesn't match filter. NICE TRY
			if(!this.filter(model, this)) return false;

			if(typeof(options.at) == 'number')
			{
				// find the correct insertion point in the master it options.at is set.
				var current		=	this.at(options.at);
				var master_idx	=	this.master.index_of(current);
				if(master_idx !== false)
				{
					options.at	=	master_idx;
				}
			}

			// add this model into the master (if it's not already in it)
			var add = this.master.upsert(model, options);
			if(this.limit) this._models.splice(this.limit);
			return add;
		},

		remove: function(model, options)
		{
			if (model instanceof Array)
			{
				return Object.each(model, function(m) { this.remove(m); }, this);
			}

			options || (options = {});
			if(typeof(options.transform) == 'undefined') options.transform = true;

			if(!this._models.contains(model)) return false;

			if(this.transform && options.transform)
			{
				model	=	this.transform.call(this, model, 'remove');
			}

			// remove the model
			this._models.erase(model);

			this.fire_event('remove', options, model);

			// remove the model from the collection
			this._remove_reference(model);
		},

		add_event: function(model, options)
		{
			if(!this.filter(model, this)) return false;
			this.refresh({silent: true});
			this.fire_event('add', options, model, this, options);
		},

		remove_event: function(model, options)
		{
			if(!this._models.contains(model)) return false;
			this.refresh({silent: true});
			this.fire_event('remove', options, model);
		},

		forward_event: function()
		{
			if(!this.forward_all_events) return false;
			this.trigger.apply(this, arguments);
		}
	});
	FilterCollection.extend	=	function(obj, base)
	{
		obj || (obj = {});
		base || (base = this);
		obj	=	Composer.Base.extend.call(this, obj, base);
		return this._do_extend(obj, base);
	};

	Composer.FilterCollection	=	FilterCollection;
	Composer._export(['FilterCollection']);
})();
<commit_msg>calling sort event on filtercollection.add_event<commit_after>/**
 * NOTE: FilterCollection is considered alpha/experimental and although this
 * most likely won't happen, it may be subject to substantial API changes. Use/
 * depend on at your own risk!
 *
 * It's also completely undocumented...good luck!
 * -----------------------------------------------------------------------------
 *
 * Composer.js is an MVC framework for creating and organizing javascript
 * applications. For documentation, please visit:
 *
 *     http://lyonbros.github.com/composer.js/
 *
 * -----------------------------------------------------------------------------
 *
 * Copyright (c) 2011, Lyon Bros Enterprises, LLC. (http://www.lyonbros.com)
 *
 * Licensed under The MIT License.
 * Redistributions of files must retain the above copyright notice.
 */
(function() {
	/**
	 * Collection that exists solely to be a "materialized view" of another
	 * "master" collection. Whenever items are added/removed from the master
	 * collection, the changes are filtered and applied to this collection as well.
	 * This is useful for keeping many collections in sync with one master list
	 * without having to manually update them all.
	 */
	var FilterCollection	=	new Class({
		Extends: Composer.Collection,

		/**
		 * Track this object's type. Useful for debugging, mainly
		 */
		__composer_type: 'filtercollection',

		master: null,
		filter: null,
		transform: null,
		limit: false,

		_do_match_action: null,

		forward_all_events: false,
		refresh_on_change: true,	// performance hit, but needed for backward compat
		sort_event: false,			// if true, fires a 'sort' event instead of 'reset' when sorting

		initialize: function(master, options)
		{
			options || (options = {});

			Object.each(Object.clone(options), function(v, k) {
				if(typeof(v) == 'function') v = v.bind(this);
				this[k]	=	v;
			}, this);

			// assign the unique app id
			this._cid	=	Composer.cid();

			this.master	=	master;

			if(!this.master) return false;
			if(!this.filter) return false;

			this.attach(options);
			if(!options.skip_initial_sync) this.refresh();
		},

		attach: function()
		{
			if(!this._do_match_action) this._do_match_action = this.match_action.bind(this);
			this.master.bind('all', this._do_match_action, 'filtercollection:'+this.cid()+':all');
			this.bind('reset', function(options) {
				options || (options = {});
				if(options.has_reload) return false;
				this.refresh(options);
			}.bind(this), 'filtercollection:reset');
		},

		detach: function()
		{
			this.master.unbind('all', 'filtercollection:'+this.cid()+':all');
			this.unbind('reset', 'filtercollection:reset');
		},

		match_action: function(event, model)
		{
			switch(event)
			{
			case 'add':
				this.add_event(model, {from_event: true}); break;
			case 'reset':
				this.refresh(); break;
			case 'clear':
				this.clear(); break;
			case 'remove':
				this.remove_event(model, {from_event: true}); break;
			case 'change':
				this.change_event(model); break;
			case 'sort':
				this.refresh(); break;
			default:
				this.forward_event.apply(this, arguments); break;
			}
		},

		refresh: function(options)
		{
			options || (options = {});

			if(options.diff_events)
			{
				var old_models	=	this._models;
			}
			this._models	=	this.master._models.filter(function(model) {
				return this.filter(model, this);
			}.bind(this));
			this.sort({silent: true});
			if(this.limit) this._models.splice(this.limit);
			if(options.diff_events)
			{
				var arrdiff	=	function(arr1, arr2) { return arr1.filter(function(el) { return !arr2.contains(el); }); };

				arrdiff(old_models, this._models).each(function(model) {
					this.fire_event('remove', options, model);
				}, this);

				arrdiff(this._models, old_models).each(function(model) {
					this.fire_event('add', options, model);
				}, this);
			}
			this.fire_event('reset', options, {has_reload: true});
		},

		change_event: function(model, options)
		{
			options || (options = {});

			// see if this model even belongs to this collection
			if(model && !this.filter(model, this)) return false;

			// track the current number of items and reloda the data
			var num_items	=	this._models.length;

			if(this.refresh_on_change)
			{
				// the brute force option (re-sort everything, re-filter everything)
				// VERY expensive
				this.refresh({silent: true});
			}
			else
			{
				// a more tactful approach
				var cur_index = this._models.indexOf(model);
				var new_index = this.sort_index(model);

				if(cur_index == -1 && this.filter(model, this))
				{
					// welcome to the team!
					this.add(model, options);
				}
				else if(cur_index > -1 && !this.filter(model, this))
				{
					// we feel that your interests no longer align with the team's
					// ...we're going to ahve to let you go.
					//
					// You BASTARDS I've poured my LIFE into this collection!!
					//
					// Yes and we're thankful for your hard work, but feel it's
					// time to move on. Your replacement is a potted plant (come
					// to think of it, so is your severance). Think of this as a
					// new beginning! Now get out of my office.
					this.remove(model, options);
				}
				else if(cur_index != new_index)
				{
					// sort order changed
					if(this.sort_event)
					{
						this.sort(Object.merge({}, options, {silent: true}));
						this.fire_event('sort', options);
					}
					else
					{
						this.sort(options);
					}
				}
			}

			// do nothing if no change
			if(this._models.length == num_items)
			{
				this.fire_event('change', options);
			}
			else
			{
				this.fire_event('reset', options);
			}
		},

		set_filter: function(fn, options)
		{
			if(!fn) return false;
			options || (options = {refresh: true});
			this.filter	=	fn.bind(this);
			if(!options.refresh) this.refresh();
		},

		add: function(data, options)
		{
			if (data instanceof Array)
			{
				return Object.each(data, function(model) { this.add(model, options); }, this);
			}

			options || (options = {});
			if(typeof(options.transform) == 'undefined') options.transform = true;

			// if we are passing raw data, create a new model from data
			var model		=	data.__composer_type == 'model' ? data : new this.master.model(data, options);

			if(this.transform && options.transform)
			{
				model	=	this.transform.call(this, model, 'add');
			}

			// model doesn't match filter. NICE TRY
			if(!this.filter(model, this)) return false;

			if(typeof(options.at) == 'number')
			{
				// find the correct insertion point in the master it options.at is set.
				var current		=	this.at(options.at);
				var master_idx	=	this.master.index_of(current);
				if(master_idx !== false)
				{
					options.at	=	master_idx;
				}
			}

			// add this model into the master (if it's not already in it)
			var add = this.master.upsert(model, options);
			if(this.limit) this._models.splice(this.limit);
			return add;
		},

		remove: function(model, options)
		{
			if (model instanceof Array)
			{
				return Object.each(model, function(m) { this.remove(m); }, this);
			}

			options || (options = {});
			if(typeof(options.transform) == 'undefined') options.transform = true;

			if(!this._models.contains(model)) return false;

			if(this.transform && options.transform)
			{
				model	=	this.transform.call(this, model, 'remove');
			}

			// remove the model
			this._models.erase(model);

			this.fire_event('remove', options, model);

			// remove the model from the collection
			this._remove_reference(model);
		},

		add_event: function(model, options)
		{
			if(!this.filter(model, this)) return false;
			this.refresh({silent: true});
			if(this.sort_event) this.fire_event('sort', options);
			this.fire_event('add', options, model, this, options);
		},

		remove_event: function(model, options)
		{
			if(!this._models.contains(model)) return false;
			this.refresh({silent: true});
			this.fire_event('remove', options, model);
		},

		forward_event: function()
		{
			if(!this.forward_all_events) return false;
			this.trigger.apply(this, arguments);
		}
	});
	FilterCollection.extend	=	function(obj, base)
	{
		obj || (obj = {});
		base || (base = this);
		obj	=	Composer.Base.extend.call(this, obj, base);
		return this._do_extend(obj, base);
	};

	Composer.FilterCollection	=	FilterCollection;
	Composer._export(['FilterCollection']);
})();
<|endoftext|>


<commit_before>var files = require('../fs/files.js');
var selftest = require('../tool-testing/selftest.js');
var testUtils = require('../tool-testing/test-utils.js');
var Sandbox = selftest.Sandbox;

var checkMobileServer = selftest.markStack(function (s, expected) {
  var output = s.read("android/project/assets/www/application/index.html");
  var mrc = testUtils.getMeteorRuntimeConfigFromHTML(output);
  selftest.expectEqual(mrc.DDP_DEFAULT_CONNECTION_URL, expected);
});

var cleanUpBuild = function (s) {
  files.rm_recursive(files.pathJoin(s.cwd, "android"));
  files.unlink(files.pathJoin(s.cwd, "myapp.tar.gz"));
};

selftest.define("cordova builds with server options", ["cordova", "slow"], function () {
  var s = new Sandbox();
  var run;

  s.createApp("myapp", "standard-app");
  s.cd("myapp");

  run = s.run("add-platform", "android");
  run.match("added");
  run.expectExit(0);

  run = s.run("build", ".");
  run.waitSecs(90);
  run.matchErr(
    "Supply the server hostname and port in the --server option");
  run.expectExit(1);

  run = s.run("build", ".", "--server", "5000");
  run.waitSecs(90);
  run.matchErr("--server must include a hostname");
  run.expectExit(1);

  run = s.run("build", ".", "--server", "https://example.com:5000");
  run.waitSecs(300);
  run.expectExit(0);
  checkMobileServer(s, "https://example.com:5000/");
  cleanUpBuild(s);

  run = s.run("build", ".", "--server", "example.com:5000");
  run.waitSecs(90);
  run.expectExit(0);
  checkMobileServer(s, "http://example.com:5000/");
  cleanUpBuild(s);

  run = s.run("build", ".", "--server", "example.com");
  run.waitSecs(90);
  run.expectExit(0);
  checkMobileServer(s, "http://example.com/");
  cleanUpBuild(s);

  run = s.run("build", ".", "--server", "https://example.com");
  run.waitSecs(90);
  run.expectExit(0);
  checkMobileServer(s, "https://example.com/");
  cleanUpBuild(s);

  // XXX COMPAT WITH 0.9.2.2
  run = s.run("build", ".", "--mobile-port", "example.com:5000");
  run.waitSecs(90);
  run.expectExit(0);
  checkMobileServer(s, "http://example.com:5000/");
  cleanUpBuild(s);
});
<commit_msg>Reenable cordova-builds self-test, which would have caught #9521.<commit_after>var files = require('../fs/files.js');
var selftest = require('../tool-testing/selftest.js');
var testUtils = require('../tool-testing/test-utils.js');
var Sandbox = selftest.Sandbox;
import { host } from "../utils/archinfo.js";
const relBuildDir = "../build";
const isOSX = host().split(".", 2).join(".") === "os.osx";

var checkMobileServer = selftest.markStack(function (s, expected) {
  function checkIndexHtml(path) {
    const output = s.read(path);
    const mrc = testUtils.getMeteorRuntimeConfigFromHTML(output);
    selftest.expectEqual(mrc.DDP_DEFAULT_CONNECTION_URL, expected);
  }

  checkIndexHtml(files.pathJoin(
    relBuildDir,
    "android/project/assets/www/application/index.html"
  ));

  if (isOSX) {
    checkIndexHtml(files.pathJoin(
      relBuildDir,
      "ios/project/www/application/index.html"
    ));
  }
});

function cleanUpBuild(s) {
  files.rm_recursive(files.pathJoin(s.cwd, relBuildDir));
}

selftest.define("cordova builds with server options", ["cordova"], function () {
  const s = new Sandbox();
  let run;

  s.createApp("myapp", "standard-app");
  s.cd("myapp");

  run = s.run("add-platform", "android");
  run.match("added");
  run.expectExit(0);

  if (isOSX) {
    run = s.run("add-platform", "ios");
    run.match("added");
    run.expectExit(0);
  }

  run = s.run("build", relBuildDir);
  run.waitSecs(90);
  run.matchErr(
    "Supply the server hostname and port in the --server option");
  run.expectExit(1);

  run = s.run("build", relBuildDir, "--server", "5000");
  run.waitSecs(90);
  run.matchErr("--server must include a hostname");
  run.expectExit(1);

  run = s.run("build", relBuildDir, "--server", "https://example.com:5000");
  run.waitSecs(300);
  run.expectExit(0);
  checkMobileServer(s, "https://example.com:5000/");
  cleanUpBuild(s);

  run = s.run("build", relBuildDir, "--server", "example.com:5000");
  run.waitSecs(90);
  run.expectExit(0);
  checkMobileServer(s, "http://example.com:5000/");
  cleanUpBuild(s);

  run = s.run("build", relBuildDir, "--server", "example.com");
  run.waitSecs(90);
  run.expectExit(0);
  checkMobileServer(s, "http://example.com/");
  cleanUpBuild(s);

  run = s.run("build", relBuildDir, "--server", "https://example.com");
  run.waitSecs(90);
  run.expectExit(0);
  checkMobileServer(s, "https://example.com/");
  cleanUpBuild(s);

  // XXX COMPAT WITH 0.9.2.2
  run = s.run("build", relBuildDir, "--mobile-port", "example.com:5000");
  run.waitSecs(90);
  run.expectExit(0);
  checkMobileServer(s, "http://example.com:5000/");
  cleanUpBuild(s);
});
<|endoftext|>


<commit_before>// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

'use strict';

const compute = require('@google-cloud/compute');
const {Storage} = require('@google-cloud/storage');

const {describe, it} = require('mocha');
const uuid = require('uuid');
const cp = require('child_process');
const {assert} = require('chai');

const instancesClient = new compute.InstancesClient({fallback: 'rest'});
const projectsClient = new compute.ProjectsClient({fallback: 'rest'});

const execSync = cmd => cp.execSync(cmd, {encoding: 'utf-8'});

describe('samples', () => {
  const instanceName = `gcloud-test-intance-${uuid.v4().split('-')[0]}`;
  const zone = 'europe-central2-b';
  const bucketName = `test-bucket-name-${uuid.v4().split('-')[0]}`;

  const storage = new Storage();

  it('should create instance', async () => {
    const projectId = await instancesClient.getProjectId();
    const output = execSync(
      `node createInstance ${projectId} ${zone} ${instanceName}`
    );
    assert.match(output, /Instance created./);
  });

  it('should print instances list', async () => {
    const projectId = await instancesClient.getProjectId();
    const output = execSync(`node listInstances ${projectId} ${zone}`);
    assert.match(output, /Instances found in zone/);
  });

  it('should print all instances list', async () => {
    const projectId = await instancesClient.getProjectId();
    const output = execSync(`node listAllInstances ${projectId}`);
    assert.match(output, /Instances found:/);
  });

  it('should delete instance', async () => {
    const projectId = await instancesClient.getProjectId();
    const output = execSync(
      `node deleteInstance ${projectId} ${zone} ${instanceName}`
    );
    assert.match(output, /Instance deleted./);
  });

  it('should wait for operation', async () => {
    const projectId = await instancesClient.getProjectId();

    const newinstanceName = `gcloud-test-intance-${uuid.v4().split('-')[0]}`;

    execSync(`node createInstance ${projectId} ${zone} ${newinstanceName}`);

    const [response] = await instancesClient.delete({
      project: projectId,
      zone,
      instance: newinstanceName,
    });

    const operationString = JSON.stringify(response.latestResponse);

    const output = execSync(
      `node waitForOperation ${projectId} '${operationString}'`
    );
    assert.match(output, /Operation finished./);
  });

  describe('usage export', () => {
    before(async () => {
      await storage.createBucket(bucketName);
    });

    after(async () => {
      const projectId = await instancesClient.getProjectId();

      await projectsClient.setUsageExportBucket({
        project: projectId,
        usageExportLocationResource: {},
      });

      await storage.bucket(bucketName).delete();
    });

    it('should set empty default value in reportNamePrefix', async () => {
      const projectId = await instancesClient.getProjectId();

      const output = execSync(
        `node setUsageExportBucket ${projectId} ${bucketName}`
      );
      await new Promise(resolve => setTimeout(resolve, 5000));

      assert.match(
        output,
        /Setting reportNamePrefix to empty value causes the report to have the default prefix value `usage_gce`./
      );

      const [project] = await projectsClient.get({
        project: projectId,
      });

      const usageExportLocation = project.usageExportLocation;

      assert.equal(usageExportLocation.bucketName, bucketName);
      assert.equal(usageExportLocation.reportNamePrefix, '');
    });

    it('should get current default value in reportNamePrefix', async () => {
      const projectId = await instancesClient.getProjectId();

      execSync(`node setUsageExportBucket ${projectId} ${bucketName}`);
      await new Promise(resolve => setTimeout(resolve, 5000));
      const output = execSync(`node getUsageExportBucket ${projectId}`);

      assert.match(
        output,
        /Report name prefix not set, replacing with default value of `usage_gce`./
      );

      assert.match(output, /Returned reportNamePrefix: usage_gce/);
    });

    it('should disable usage export', async () => {
      const projectId = await instancesClient.getProjectId();

      execSync(`node setUsageExportBucket ${projectId} ${bucketName}`);
      await new Promise(resolve => setTimeout(resolve, 5000));
      execSync(`node disableUsageExport ${projectId}`);
      await new Promise(resolve => setTimeout(resolve, 5000));

      const [project] = await projectsClient.get({
        project: projectId,
      });

      assert.isUndefined(project.usageExportLocation);
    });
  });

  describe('pagination', () => {
    const projectId = 'windows-sql-cloud';

    it('should automatically iterate throught the pages', async () => {
      const output = execSync(`node listImages ${projectId}`);
      const lines = output.split(' - ');

      assert(lines.length > 3);
    });

    it('should iterate page by page granularly', async () => {
      const output = execSync(`node listImagesByPage ${projectId}`);

      assert.match(output, /Page 1/);
      assert.match(output, /Page 2/);
    });
  });
});
<commit_msg>test: address race condition with setUsageExportBucket (#622)<commit_after>// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

'use strict';

const compute = require('@google-cloud/compute');
const {Storage} = require('@google-cloud/storage');

const {describe, it} = require('mocha');
const uuid = require('uuid');
const cp = require('child_process');
const {assert} = require('chai');

const instancesClient = new compute.InstancesClient({fallback: 'rest'});
const projectsClient = new compute.ProjectsClient({fallback: 'rest'});

const execSync = cmd => cp.execSync(cmd, {encoding: 'utf-8'});

// A helper for delaying integration tests with an exponential backoff.
// See examples like: https://github.com/googleapis/nodejs-monitoring/issues/190,
// https://github.com/googleapis/nodejs-monitoring/issues/191.
const delay = async test => {
  const retries = test.currentRetry();
  if (retries === 0) return; // no retry on the first failure.
  // see: https://cloud.google.com/storage/docs/exponential-backoff:
  const ms = Math.pow(2, retries) * 500 + Math.random() * 1000;
  return new Promise(done => {
    console.info(`retrying "${test.title}" in ${ms}ms`);
    setTimeout(done, ms);
  });
};

describe('samples', () => {
  const instanceName = `gcloud-test-intance-${uuid.v4().split('-')[0]}`;
  const zone = 'europe-central2-b';
  const bucketName = `test-bucket-name-${uuid.v4().split('-')[0]}`;

  const storage = new Storage();

  it('should create instance', async () => {
    const projectId = await instancesClient.getProjectId();
    const output = execSync(
      `node createInstance ${projectId} ${zone} ${instanceName}`
    );
    assert.match(output, /Instance created./);
  });

  it('should print instances list', async () => {
    const projectId = await instancesClient.getProjectId();
    const output = execSync(`node listInstances ${projectId} ${zone}`);
    assert.match(output, /Instances found in zone/);
  });

  it('should print all instances list', async () => {
    const projectId = await instancesClient.getProjectId();
    const output = execSync(`node listAllInstances ${projectId}`);
    assert.match(output, /Instances found:/);
  });

  it('should delete instance', async () => {
    const projectId = await instancesClient.getProjectId();
    const output = execSync(
      `node deleteInstance ${projectId} ${zone} ${instanceName}`
    );
    assert.match(output, /Instance deleted./);
  });

  it('should wait for operation', async () => {
    const projectId = await instancesClient.getProjectId();

    const newinstanceName = `gcloud-test-intance-${uuid.v4().split('-')[0]}`;

    execSync(`node createInstance ${projectId} ${zone} ${newinstanceName}`);

    const [response] = await instancesClient.delete({
      project: projectId,
      zone,
      instance: newinstanceName,
    });

    const operationString = JSON.stringify(response.latestResponse);

    const output = execSync(
      `node waitForOperation ${projectId} '${operationString}'`
    );
    assert.match(output, /Operation finished./);
  });

  describe('usage export', () => {
    before(async () => {
      try {
        await storage.createBucket(bucketName);
      } catch (err) {
        // Resource likely already existed due to retry.
      }
    });

    after(async () => {
      const projectId = await instancesClient.getProjectId();

      await projectsClient.setUsageExportBucket({
        project: projectId,
        usageExportLocationResource: {},
      });

      await storage.bucket(bucketName).delete();
    });

    it('should set empty default value in reportNamePrefix', async function () {
      this.retries(3);
      await delay(this.test);
      const projectId = await instancesClient.getProjectId();

      const output = execSync(
        `node setUsageExportBucket ${projectId} ${bucketName}`
      );
      await new Promise(resolve => setTimeout(resolve, 5000));

      assert.match(
        output,
        /Setting reportNamePrefix to empty value causes the report to have the default prefix value `usage_gce`./
      );

      const [project] = await projectsClient.get({
        project: projectId,
      });

      const usageExportLocation = project.usageExportLocation;

      assert.match(usageExportLocation.bucketName, /test-bucket-name/);
      assert.equal(usageExportLocation.reportNamePrefix, '');
    });

    it('should get current default value in reportNamePrefix', async function () {
      this.retries(3);
      await delay(this.test);
      const projectId = await instancesClient.getProjectId();

      execSync(`node setUsageExportBucket ${projectId} ${bucketName}`);
      await new Promise(resolve => setTimeout(resolve, 5000));
      const output = execSync(`node getUsageExportBucket ${projectId}`);

      assert.match(
        output,
        /Report name prefix not set, replacing with default value of `usage_gce`./
      );

      assert.match(output, /Returned reportNamePrefix: usage_gce/);
    });

    it('should disable usage export', async function () {
      this.retries(3);
      await delay(this.test);
      const projectId = await instancesClient.getProjectId();

      execSync(`node setUsageExportBucket ${projectId} ${bucketName}`);
      await new Promise(resolve => setTimeout(resolve, 5000));
      execSync(`node disableUsageExport ${projectId}`);
      await new Promise(resolve => setTimeout(resolve, 5000));

      const [project] = await projectsClient.get({
        project: projectId,
      });

      assert.isUndefined(project.usageExportLocation);
    });
  });

  describe('pagination', () => {
    const projectId = 'windows-sql-cloud';

    it('should automatically iterate throught the pages', async () => {
      const output = execSync(`node listImages ${projectId}`);
      const lines = output.split(' - ');

      assert(lines.length > 3);
    });

    it('should iterate page by page granularly', async () => {
      const output = execSync(`node listImagesByPage ${projectId}`);

      assert.match(output, /Page 1/);
      assert.match(output, /Page 2/);
    });
  });
});
<|endoftext|>