<commit_before>/* @flow */
/* eslint-disable no-console,import/no-extraneous-dependencies */

const autoprefixer = require('autoprefixer');
const path = require('path');
const webpack = require('webpack');
const AssetsPlugin = require('assets-webpack-plugin');
const nodeExternals = require('webpack-node-externals');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const WebpackMd5Hash = require('webpack-md5-hash');
const { removeEmpty, ifElse, merge } = require('./utils');

const appRootPath = process.cwd();

function webpackConfigFactory(options /*: Object */, args /*: Object */) /*: Object */ {
  const { target, mode } = options;
  const { eslint = require.resolve('../../config/eslint/default') } = args;

  const isDev = mode === 'development';
  const isProd = mode === 'production';
  const isClient = target === 'client';
  const isServer = target === 'server';

  const ifDev = ifElse(isDev);
  const ifProd = ifElse(isProd);
  const ifClient = ifElse(isClient);
  const ifServer = ifElse(isServer);
  const ifDevClient = ifElse(isDev && isClient);
  const ifDevServer = ifElse(isDev && isServer);
  const ifProdClient = ifElse(isProd && isClient);

  const CLIENT_DEVSERVER_PORT = process.env.CLIENT_DEVSERVER_PORT || 7000;
  const CLIENT_BUNDLE_OUTPUT_PATH = process.env.CLIENT_BUNDLE_OUTPUT_PATH || './build/server';
  const SERVER_BUNDLE_OUTPUT_PATH = process.env.SERVER_BUNDLE_OUTPUT_PATH || './build/client';
  const CLIENT_BUNDLE_HTTP_PATH = process.env.CLIENT_BUNDLE_HTTP_PATH || '/client-assets/';
  const SERVER_PORT = process.env.SERVER_PORT || 3000;
  const DISABLE_SSR = process.env.DISABLE_SSR ? process.env.DISABLE_SSR : false;
  const CLIENT_BUNDLE_ASSETS_FILENAME = process.env.CLIENT_BUNDLE_ASSETS_FILENAME || 'assets.json';
  const CLIENT_BUNDLE_CACHE_MAXAGE = process.env.CLIENT_BUNDLE_CACHE_MAXAGE || '365d';

  return {
    // We need to state that we are targetting "node" for our server bundle.
    target: ifServer('node', 'web'),
    // We have to set this to be able to use these items when executing a
    // server bundle.  Otherwise strangeness happens, like __dirname resolving
    // to '/'.  There is no effect on our client bundle.
    node: {
      __dirname: true,
      __filename: true,
    },
    // Anything listed in externals will not be included in our bundle.
    externals: removeEmpty(
      // We don't want our node_modules to be bundled with our server package,
      // prefering them to be resolved via native node module system.  Therefore
      // we use the `webpack-node-externals` library to help us generate an
      // externals config that will ignore all node_modules.
      ifServer(nodeExternals({
        // NOTE: !!!
        // However the node_modules may contain files that will rely on our
        // webpack loaders in order to be used/resolved, for example CSS or
        // SASS. For these cases please make sure that the file extensions
        // are added to the below list. We have added the most common formats.
        whitelist: [
          /\.(eot|woff|woff2|ttf|otf)$/,
          /\.(svg|png|jpg|jpeg|gif|ico)$/,
          /\.(mp4|mp3|ogg|swf|webp)$/,
          /\.(css|scss|sass|sss|less)$/,
        ],
      }))
    ),
    devtool: ifElse(isServer || isDev)(
      // We want to be able to get nice stack traces when running our server
      // bundle.  To fully support this we'll also need to configure the
      // `node-source-map-support` module to execute at the start of the server
      // bundle.  This module will allow the node to make use of the
      // source maps.
      // We also want to be able to link to the source in chrome dev tools
      // whilst we are in development mode. :)
      'source-map',
      // When in production client mode we don't want any source maps to
      // decrease our payload sizes.
      // This form has almost no cost.
      'hidden-source-map'
    ),
    // Define our entry chunks for our bundle.
    entry: merge(
      {
        main: removeEmpty(
          ifDevClient(require.resolve('react-hot-loader/patch')),
          ifDevClient(`${require.resolve('webpack-hot-middleware/client')}?reload=true&path=http://localhost:${CLIENT_DEVSERVER_PORT}/__webpack_hmr`),
          path.resolve(appRootPath, `./src/${target}/index.js`)
        ),
      }
    ),
    output: {
      // The dir in which our bundle should be output.
      path: path.resolve(
        appRootPath,
        isClient
          ? CLIENT_BUNDLE_OUTPUT_PATH
          : SERVER_BUNDLE_OUTPUT_PATH
      ),
      // The filename format for our bundle's entries.
      filename: ifProdClient(
        // We include a hash for client caching purposes.  Including a unique
        // has for every build will ensure browsers always fetch our newest
        // bundle.
        '[name]-[chunkhash].js',
        // We want a determinable file name when running our server bundles,
        // as we need to be able to target our server start file from our
        // npm scripts.  We don't care about caching on the server anyway.
        // We also want our client development builds to have a determinable
        // name for our hot reloading client bundle server.
        '[name].js'
      ),
      chunkFilename: '[name]-[chunkhash].js',
      // This is the web path under which our webpack bundled output should
      // be considered as being served from.
      publicPath: ifDev(
        // As we run a seperate server for our client and server bundles we
        // need to use an absolute http path for our assets public path.
        `http://localhost:${CLIENT_DEVSERVER_PORT}${CLIENT_BUNDLE_HTTP_PATH}`,
        // Otherwise we expect our bundled output to be served from this path.
        CLIENT_BUNDLE_HTTP_PATH
      ),
      // When in server mode we will output our bundle as a commonjs2 module.
      libraryTarget: ifServer('commonjs2', 'var'),
    },
    resolve: {
      // These extensions are tried when resolving a file.
      extensions: [
        '.js',
        '.jsx',
        '.json',
        '.css',
        '.scss',
      ],
      modulesDirectories: [
        'node_modules',
        path.resolve(__dirname, '../../../../node_modules'),
        path.resolve(__dirname, '../../../../../node_modules'),
      ],
    },
    postcss: [autoprefixer],
    plugins: removeEmpty(
      // We use this so that our generated [chunkhash]'s are only different if
      // the content for our respective chunks have changed.  This optimises
      // our long term browser caching strategy for our client bundle, avoiding
      // cases where browsers end up having to download all the client chunks
      // even though 1 or 2 may have only changed.
      ifClient(new WebpackMd5Hash()),

      // Each key passed into DefinePlugin is an identifier.
      // The values for each key will be inlined into the code replacing any
      // instances of the keys that are found.
      // If the value is a string it will be used as a code fragment.
      // If the value isn’t a string, it will be stringified (including functions).
      // If the value is an object all keys are removeEmpty the same way.
      // If you prefix typeof to the key, it’s only removeEmpty for typeof calls.
      new webpack.DefinePlugin({
        'process.env': {
          // NOTE: The NODE_ENV key is especially important for production
          // builds as React relies on process.env.NODE_ENV for optimizations.
          NODE_ENV: JSON.stringify(mode),
          IS_CLIENT: JSON.stringify(isClient),
          IS_SERVER: JSON.stringify(isServer),
          // All the below items match the config items in our .env file. Go
          // to the .env_example for a description of each key.
          SERVER_PORT: JSON.stringify(SERVER_PORT),
          CLIENT_DEVSERVER_PORT: JSON.stringify(CLIENT_DEVSERVER_PORT),
          DISABLE_SSR: JSON.stringify(DISABLE_SSR),
          SERVER_BUNDLE_OUTPUT_PATH: JSON.stringify(SERVER_BUNDLE_OUTPUT_PATH),
          CLIENT_BUNDLE_OUTPUT_PATH: JSON.stringify(CLIENT_BUNDLE_OUTPUT_PATH),
          CLIENT_BUNDLE_ASSETS_FILENAME: JSON.stringify(CLIENT_BUNDLE_ASSETS_FILENAME),
          CLIENT_BUNDLE_HTTP_PATH: JSON.stringify(CLIENT_BUNDLE_HTTP_PATH),
          CLIENT_BUNDLE_CACHE_MAXAGE: JSON.stringify(CLIENT_BUNDLE_CACHE_MAXAGE),
        },
      }),

      ifClient(
        // Generates a JSON file containing a map of all the output files for
        // our webpack bundle.  A necessisty for our server rendering process
        // as we need to interogate these files in order to know what JS/CSS
        // we need to inject into our HTML.
        new AssetsPlugin({
          filename: CLIENT_BUNDLE_ASSETS_FILENAME,
          path: path.resolve(appRootPath, CLIENT_BUNDLE_OUTPUT_PATH),
        })
      ),

      // We don't want webpack errors to occur during development as it will
      // kill our dev servers.
      ifDev(new webpack.NoErrorsPlugin()),

      // We need this plugin to enable hot module reloading for our dev server.
      ifDevClient(new webpack.HotModuleReplacementPlugin()),

      // Ensure only 1 file is output for the server bundles.  This makes it
      // much easer for us to clear the module cache when reloading the server.
      ifDevServer(new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1 })),

      // Adds options to all of our loaders.
      ifProdClient(
        new webpack.LoaderOptionsPlugin({
          // Indicates to our loaders that they should minify their output
          // if they have the capability to do so.
          minimize: true,
          // Indicates to our loaders that they should enter into debug mode
          // should they support it.
          debug: false,
        })
      ),

      ifProdClient(
        // JS Minification.
        new webpack.optimize.UglifyJsPlugin({
          compress: {
            screw_ie8: true,
            warnings: false,
          },
        })
      ),

      ifProd(
        // This is actually only useful when our deps are installed via npm2.
        // In npm2 its possible to get duplicates of dependencies bundled
        // given the nested module structure. npm3 is flat, so this doesn't
        // occur.
        new webpack.optimize.DedupePlugin()
      ),

      ifProdClient(
        // This is a production client so we will extract our CSS into
        // CSS files.
        new ExtractTextPlugin({ filename: '[name]-[chunkhash].css', allChunks: true })
      )
    ),
    module: {
      preLoaders: [
        {
          test: /\.jsx?$/,
          loader: require.resolve('eslint-loader'),
          exclude: /node_modules/,
          query: {
            baseConfig: false,
            configFile: eslint
          }
        }
      ],
      loaders: [
        // Javascript
        {
          test: /\.jsx?$/,
          loader: require.resolve('babel-loader'),
          exclude: [
            /node_modules/,
            path.resolve(appRootPath, CLIENT_BUNDLE_OUTPUT_PATH),
            path.resolve(appRootPath, SERVER_BUNDLE_OUTPUT_PATH),
          ],
          query: merge(
            {
              env: {
                development: {
                  plugins: [require.resolve('react-hot-loader/babel')],
                },
              },
              plugins: [require.resolve('babel-plugin-transform-object-rest-spread')],
            },
            ifServer({
              // We are running a node 6 server which has support for almost
              // all of the ES2015 syntax, therefore we only transpile JSX.
              presets: [
                [
                  require.resolve('babel-preset-latest-node6'),
                  {
                    'object-rest': true
                  }
                ],
                require.resolve('babel-preset-react'),
              ],
            }),
            ifClient({
              // For our clients code we will need to transpile our JS into
              // ES5 code for wider browser/device compatability.
              presets: [
                // JSX
                require.resolve('babel-preset-react'),
                // Webpack 2 includes support for es2015 imports, therefore we
                // disable the modules processing.
                [require.resolve('babel-preset-latest'), { es2015: { modules: false } }],
              ],
            })
          ),
        },

        // JSON
        {
          test: /\.json$/,
          loader: require.resolve('json-loader'),
        },

        // Images and Fonts
        {
          test: /\.(jpg|jpeg|png|gif|ico|eot|svg|ttf|woff|woff2|otf)$/,
          loader: require.resolve('url-loader'),
          query: {
            // Any file with a byte smaller than this will be "inlined" via
            // a base64 representation.
            limit: 10000,
            // We only emit files when building a client bundle, for the server
            // bundles this will just make sure any file imports will not fall
            // over.
            emitFile: isClient,
          },
        },

        // (S)CSS
        merge(
          { test: /(\.css)$/ },
          // When targetting the server we use the "/locals" version of the
          // css loader.
          ifServer({
            loaders: [
              `${require.resolve('css-loader/locals')}?modules`,
              require.resolve('postcss-loader'),
            ],
          }),
          // For a production client build we use the ExtractTextPlugin which
          // will extract our CSS into CSS files.  The plugin needs to be
          // registered within the plugins section too.
          ifProdClient({
            loader: ExtractTextPlugin.extract({
              notExtractLoader: require.resolve('style-loader'),
              loader: [`${require.resolve('css-loader')}?modules`, require.resolve('postcss-loader')],
            }),
          }),
          // For a development client we will use a straight style & css loader
          // along with source maps.  This combo gives us a better development
          // experience.
          ifDevClient({
            loaders: [
              require.resolve('style-loader'),
              { loader: require.resolve('css-loader'), query: { modules: true, sourceMap: true } },
              require.resolve('postcss-loader'),
            ],
          })
        ),
      ],
    },
  };
}

module.exports = webpackConfigFactory;
<commit_msg>:bug: fix bundle output paths<commit_after>/* @flow */
/* eslint-disable no-console,import/no-extraneous-dependencies */

const autoprefixer = require('autoprefixer');
const path = require('path');
const webpack = require('webpack');
const AssetsPlugin = require('assets-webpack-plugin');
const nodeExternals = require('webpack-node-externals');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const WebpackMd5Hash = require('webpack-md5-hash');
const { removeEmpty, ifElse, merge } = require('./utils');

const appRootPath = process.cwd();

function webpackConfigFactory(options /*: Object */, args /*: Object */) /*: Object */ {
  const { target, mode } = options;
  const { eslint = require.resolve('../../config/eslint/default') } = args;

  const isDev = mode === 'development';
  const isProd = mode === 'production';
  const isClient = target === 'client';
  const isServer = target === 'server';

  const ifDev = ifElse(isDev);
  const ifProd = ifElse(isProd);
  const ifClient = ifElse(isClient);
  const ifServer = ifElse(isServer);
  const ifDevClient = ifElse(isDev && isClient);
  const ifDevServer = ifElse(isDev && isServer);
  const ifProdClient = ifElse(isProd && isClient);

  const CLIENT_DEVSERVER_PORT = process.env.CLIENT_DEVSERVER_PORT || 7000;
  const CLIENT_BUNDLE_OUTPUT_PATH = process.env.CLIENT_BUNDLE_OUTPUT_PATH || './build/client';
  const SERVER_BUNDLE_OUTPUT_PATH = process.env.SERVER_BUNDLE_OUTPUT_PATH || './build/server';
  const CLIENT_BUNDLE_HTTP_PATH = process.env.CLIENT_BUNDLE_HTTP_PATH || '/client-assets/';
  const SERVER_PORT = process.env.SERVER_PORT || 3000;
  const DISABLE_SSR = process.env.DISABLE_SSR ? process.env.DISABLE_SSR : false;
  const CLIENT_BUNDLE_ASSETS_FILENAME = process.env.CLIENT_BUNDLE_ASSETS_FILENAME || 'assets.json';
  const CLIENT_BUNDLE_CACHE_MAXAGE = process.env.CLIENT_BUNDLE_CACHE_MAXAGE || '365d';

  return {
    // We need to state that we are targetting "node" for our server bundle.
    target: ifServer('node', 'web'),
    // We have to set this to be able to use these items when executing a
    // server bundle.  Otherwise strangeness happens, like __dirname resolving
    // to '/'.  There is no effect on our client bundle.
    node: {
      __dirname: true,
      __filename: true,
    },
    // Anything listed in externals will not be included in our bundle.
    externals: removeEmpty(
      // We don't want our node_modules to be bundled with our server package,
      // prefering them to be resolved via native node module system.  Therefore
      // we use the `webpack-node-externals` library to help us generate an
      // externals config that will ignore all node_modules.
      ifServer(nodeExternals({
        // NOTE: !!!
        // However the node_modules may contain files that will rely on our
        // webpack loaders in order to be used/resolved, for example CSS or
        // SASS. For these cases please make sure that the file extensions
        // are added to the below list. We have added the most common formats.
        whitelist: [
          /\.(eot|woff|woff2|ttf|otf)$/,
          /\.(svg|png|jpg|jpeg|gif|ico)$/,
          /\.(mp4|mp3|ogg|swf|webp)$/,
          /\.(css|scss|sass|sss|less)$/,
        ],
      }))
    ),
    devtool: ifElse(isServer || isDev)(
      // We want to be able to get nice stack traces when running our server
      // bundle.  To fully support this we'll also need to configure the
      // `node-source-map-support` module to execute at the start of the server
      // bundle.  This module will allow the node to make use of the
      // source maps.
      // We also want to be able to link to the source in chrome dev tools
      // whilst we are in development mode. :)
      'source-map',
      // When in production client mode we don't want any source maps to
      // decrease our payload sizes.
      // This form has almost no cost.
      'hidden-source-map'
    ),
    // Define our entry chunks for our bundle.
    entry: merge(
      {
        main: removeEmpty(
          ifDevClient(require.resolve('react-hot-loader/patch')),
          ifDevClient(`${require.resolve('webpack-hot-middleware/client')}?reload=true&path=http://localhost:${CLIENT_DEVSERVER_PORT}/__webpack_hmr`),
          path.resolve(appRootPath, `./src/${target}/index.js`)
        ),
      }
    ),
    output: {
      // The dir in which our bundle should be output.
      path: path.resolve(
        appRootPath,
        isClient
          ? CLIENT_BUNDLE_OUTPUT_PATH
          : SERVER_BUNDLE_OUTPUT_PATH
      ),
      // The filename format for our bundle's entries.
      filename: ifProdClient(
        // We include a hash for client caching purposes.  Including a unique
        // has for every build will ensure browsers always fetch our newest
        // bundle.
        '[name]-[chunkhash].js',
        // We want a determinable file name when running our server bundles,
        // as we need to be able to target our server start file from our
        // npm scripts.  We don't care about caching on the server anyway.
        // We also want our client development builds to have a determinable
        // name for our hot reloading client bundle server.
        '[name].js'
      ),
      chunkFilename: '[name]-[chunkhash].js',
      // This is the web path under which our webpack bundled output should
      // be considered as being served from.
      publicPath: ifDev(
        // As we run a seperate server for our client and server bundles we
        // need to use an absolute http path for our assets public path.
        `http://localhost:${CLIENT_DEVSERVER_PORT}${CLIENT_BUNDLE_HTTP_PATH}`,
        // Otherwise we expect our bundled output to be served from this path.
        CLIENT_BUNDLE_HTTP_PATH
      ),
      // When in server mode we will output our bundle as a commonjs2 module.
      libraryTarget: ifServer('commonjs2', 'var'),
    },
    resolve: {
      // These extensions are tried when resolving a file.
      extensions: [
        '.js',
        '.jsx',
        '.json',
        '.css',
        '.scss',
      ],
      modulesDirectories: [
        'node_modules',
        path.resolve(__dirname, '../../../../node_modules'),
        path.resolve(__dirname, '../../../../../node_modules'),
      ],
    },
    postcss: [autoprefixer],
    plugins: removeEmpty(
      // We use this so that our generated [chunkhash]'s are only different if
      // the content for our respective chunks have changed.  This optimises
      // our long term browser caching strategy for our client bundle, avoiding
      // cases where browsers end up having to download all the client chunks
      // even though 1 or 2 may have only changed.
      ifClient(new WebpackMd5Hash()),

      // Each key passed into DefinePlugin is an identifier.
      // The values for each key will be inlined into the code replacing any
      // instances of the keys that are found.
      // If the value is a string it will be used as a code fragment.
      // If the value isn’t a string, it will be stringified (including functions).
      // If the value is an object all keys are removeEmpty the same way.
      // If you prefix typeof to the key, it’s only removeEmpty for typeof calls.
      new webpack.DefinePlugin({
        'process.env': {
          // NOTE: The NODE_ENV key is especially important for production
          // builds as React relies on process.env.NODE_ENV for optimizations.
          NODE_ENV: JSON.stringify(mode),
          IS_CLIENT: JSON.stringify(isClient),
          IS_SERVER: JSON.stringify(isServer),
          // All the below items match the config items in our .env file. Go
          // to the .env_example for a description of each key.
          SERVER_PORT: JSON.stringify(SERVER_PORT),
          CLIENT_DEVSERVER_PORT: JSON.stringify(CLIENT_DEVSERVER_PORT),
          DISABLE_SSR: JSON.stringify(DISABLE_SSR),
          SERVER_BUNDLE_OUTPUT_PATH: JSON.stringify(SERVER_BUNDLE_OUTPUT_PATH),
          CLIENT_BUNDLE_OUTPUT_PATH: JSON.stringify(CLIENT_BUNDLE_OUTPUT_PATH),
          CLIENT_BUNDLE_ASSETS_FILENAME: JSON.stringify(CLIENT_BUNDLE_ASSETS_FILENAME),
          CLIENT_BUNDLE_HTTP_PATH: JSON.stringify(CLIENT_BUNDLE_HTTP_PATH),
          CLIENT_BUNDLE_CACHE_MAXAGE: JSON.stringify(CLIENT_BUNDLE_CACHE_MAXAGE),
        },
      }),

      ifClient(
        // Generates a JSON file containing a map of all the output files for
        // our webpack bundle.  A necessisty for our server rendering process
        // as we need to interogate these files in order to know what JS/CSS
        // we need to inject into our HTML.
        new AssetsPlugin({
          filename: CLIENT_BUNDLE_ASSETS_FILENAME,
          path: path.resolve(appRootPath, CLIENT_BUNDLE_OUTPUT_PATH),
        })
      ),

      // We don't want webpack errors to occur during development as it will
      // kill our dev servers.
      ifDev(new webpack.NoErrorsPlugin()),

      // We need this plugin to enable hot module reloading for our dev server.
      ifDevClient(new webpack.HotModuleReplacementPlugin()),

      // Ensure only 1 file is output for the server bundles.  This makes it
      // much easer for us to clear the module cache when reloading the server.
      ifDevServer(new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1 })),

      // Adds options to all of our loaders.
      ifProdClient(
        new webpack.LoaderOptionsPlugin({
          // Indicates to our loaders that they should minify their output
          // if they have the capability to do so.
          minimize: true,
          // Indicates to our loaders that they should enter into debug mode
          // should they support it.
          debug: false,
        })
      ),

      ifProdClient(
        // JS Minification.
        new webpack.optimize.UglifyJsPlugin({
          compress: {
            screw_ie8: true,
            warnings: false,
          },
        })
      ),

      ifProd(
        // This is actually only useful when our deps are installed via npm2.
        // In npm2 its possible to get duplicates of dependencies bundled
        // given the nested module structure. npm3 is flat, so this doesn't
        // occur.
        new webpack.optimize.DedupePlugin()
      ),

      ifProdClient(
        // This is a production client so we will extract our CSS into
        // CSS files.
        new ExtractTextPlugin({ filename: '[name]-[chunkhash].css', allChunks: true })
      )
    ),
    module: {
      preLoaders: [
        {
          test: /\.jsx?$/,
          loader: require.resolve('eslint-loader'),
          exclude: /node_modules/,
          query: {
            baseConfig: false,
            configFile: eslint
          }
        }
      ],
      loaders: [
        // Javascript
        {
          test: /\.jsx?$/,
          loader: require.resolve('babel-loader'),
          exclude: [
            /node_modules/,
            path.resolve(appRootPath, CLIENT_BUNDLE_OUTPUT_PATH),
            path.resolve(appRootPath, SERVER_BUNDLE_OUTPUT_PATH),
          ],
          query: merge(
            {
              env: {
                development: {
                  plugins: [require.resolve('react-hot-loader/babel')],
                },
              },
              plugins: [require.resolve('babel-plugin-transform-object-rest-spread')],
            },
            ifServer({
              // We are running a node 6 server which has support for almost
              // all of the ES2015 syntax, therefore we only transpile JSX.
              presets: [
                [
                  require.resolve('babel-preset-latest-node6'),
                  {
                    'object-rest': true
                  }
                ],
                require.resolve('babel-preset-react'),
              ],
            }),
            ifClient({
              // For our clients code we will need to transpile our JS into
              // ES5 code for wider browser/device compatability.
              presets: [
                // JSX
                require.resolve('babel-preset-react'),
                // Webpack 2 includes support for es2015 imports, therefore we
                // disable the modules processing.
                [require.resolve('babel-preset-latest'), { es2015: { modules: false } }],
              ],
            })
          ),
        },

        // JSON
        {
          test: /\.json$/,
          loader: require.resolve('json-loader'),
        },

        // Images and Fonts
        {
          test: /\.(jpg|jpeg|png|gif|ico|eot|svg|ttf|woff|woff2|otf)$/,
          loader: require.resolve('url-loader'),
          query: {
            // Any file with a byte smaller than this will be "inlined" via
            // a base64 representation.
            limit: 10000,
            // We only emit files when building a client bundle, for the server
            // bundles this will just make sure any file imports will not fall
            // over.
            emitFile: isClient,
          },
        },

        // (S)CSS
        merge(
          { test: /(\.css)$/ },
          // When targetting the server we use the "/locals" version of the
          // css loader.
          ifServer({
            loaders: [
              `${require.resolve('css-loader/locals')}?modules`,
              require.resolve('postcss-loader'),
            ],
          }),
          // For a production client build we use the ExtractTextPlugin which
          // will extract our CSS into CSS files.  The plugin needs to be
          // registered within the plugins section too.
          ifProdClient({
            loader: ExtractTextPlugin.extract({
              notExtractLoader: require.resolve('style-loader'),
              loader: [`${require.resolve('css-loader')}?modules`, require.resolve('postcss-loader')],
            }),
          }),
          // For a development client we will use a straight style & css loader
          // along with source maps.  This combo gives us a better development
          // experience.
          ifDevClient({
            loaders: [
              require.resolve('style-loader'),
              { loader: require.resolve('css-loader'), query: { modules: true, sourceMap: true } },
              require.resolve('postcss-loader'),
            ],
          })
        ),
      ],
    },
  };
}

module.exports = webpackConfigFactory;
<|endoftext|>


<commit_before>function listProcessor(input) {
    let list = input.shift().split(" ");
    console.log(list.join(" "));

    for (let command of input) {
        let tokens = command.split(/\s+/g);
        let commandProperties = tokens.slice(1);

        processCommand(tokens[0], commandProperties);
    }

    function processCommand(command, tokens) {
        let index;

        switch (command) {
            case 'append':
                if (tokens.length !== 1 || tokens[0].trim() === ''){
                    console.log('Error: invalid command parameters')
                }

                list.push(tokens[0]);
                console.log(list.join(" "));
                break;
            case 'prepend':
                if (tokens.length !== 1 || tokens[0].trim() === ''){
                    console.log('Error: invalid command parameters')
                }

                list.unshift(tokens[0]);
                console.log(list.join(" "));
                break;
            case 'reverse':
                if (tokens.length !== 0){
                    console.log('Error: invalid command parameters')
                    break;
                }

                list = list.reverse();
                console.log(list.join(" "));
                break;
            case 'insert':
                if (tokens.length !== 2 || !Number.isInteger(Number.parseInt(tokens[0])) || tokens[1].trim() === ''){
                    console.log('Error: invalid command parameters');
                    break;
                }

                index = Number(tokens[0]);
                let stringToInsert = tokens[1];

                if (index < 0 || index >= list.length) {
                    console.log(`Error: invalid index ${index}`);
                    break;
                }

                list.splice(index, 0, stringToInsert);
                console.log(list.join(" "));
                break;
            case 'delete':
                if (tokens.length !== 1 || !Number.isInteger(Number.parseInt(tokens[0]))){
                    console.log('Error: invalid command parameters');
                    break;
                }

                 index = Number(tokens[0]);

                if (index < 0 || index >= list.length) {
                    console.log(`Error: invalid index ${index}`);
                    break;
                }

                list.splice(index, 1);
                console.log(list.join(" "));
                break;
            case 'roll':
                //CHECKKKKK
                if(tokens.length !== 1 || !(tokens[0] === 'left' || tokens[0] === 'right')){
                    console.log('Error: invalid command parameters');
                    break;
                }
                if (tokens[0] === 'left') {
                    let firstItem = list.shift();
                    list.push(firstItem);
                    console.log(list.join(" "));
                    break;
                } else if (tokens[0] === 'right'){
                    let lastItem = list.pop();
                    list.unshift(lastItem);
                    console.log(list.join(" "));
                }

                break;
            case 'sort':
                if(tokens.length !== 0){
                    console.log('Error: invalid command parameters');
                    break;
                }

                list = list.sort(function(a,b){
                    return a.localeCompare(b);
                });

                console.log(list.join(" "));
                break;
            case 'count':
                if(tokens.length !== 1 || tokens[0] === ''){
                    console.log('Error: invalid command parameters')
                    break;
                }

                let counter = 0;
                for (let item of list) {
                    if (item === tokens[0]){
                        counter++;
                    }
                }

                console.log(counter);
                break;
            case 'end':
                if (tokens.length !== 0){
                    console.log('Error: invalid command parameters');
                    break;
                }

                console.log('Finished');
                break;
            default:
                console.log('Error: invalid command');
        }
    }
}

listProcessor([
    'alpha beta gamma',
    'append delta',
    'prepend 00',
    'invalid command',
    'insert 1 ins',
    'insert -1 ins',
    'insert 1',
    'delete 3',
    'delete invalid',
    'delete 5',
    'roll left',
    'roll',
    'roll right',
    'just roll',
    'reverse',
    'sort',
    'sort 3',
    'count alpha',
    'append alpha',
    'count alpha',
    'count ALPHA',
    'end 1',
    'END',
    'end'
]);<commit_msg>Added error handling<commit_after>function listProcessor(input) {
    let list = input.shift().split(" ");
    console.log(list.join(" "));

    for (let command of input) {
        let tokens = command.split(/\s+/g);
        let commandProperties = tokens.slice(1);
        try {
            let result = processCommand(tokens[0], commandProperties);
            console.log(result);
        }
        catch(err) {
            console.log(err.message)
        }
    }

    function processCommand(command, tokens) {
        let index;

        switch (command) {
            case 'append':
                if (tokens.length !== 1 || tokens[0].trim() === ''){
                    throw new Error('Error: invalid command parameters');
                }

                list.push(tokens[0]);

                return list.join(" ");
            case 'prepend':
                if (tokens.length !== 1 || tokens[0].trim() === ''){
                    throw new Error('Error: invalid command parameters');
                }

                list.unshift(tokens[0]);

                return list.join(" ");
            case 'reverse':
                if (tokens.length !== 0){
                    throw new Error('Error: invalid command parameters');
                }

                list = list.reverse();

                return list.join(" ");
            case 'insert':
                if (tokens.length !== 2 || !Number.isInteger(Number.parseInt(tokens[0])) || tokens[1].trim() === ''){
                    throw new Error('Error: invalid command parameters');
                }

                index = Number(tokens[0]);
                let stringToInsert = tokens[1];

                if (index < 0 || index >= list.length) {
                    throw new Error(`Error: invalid index ${index}`);
                }

                list.splice(index, 0, stringToInsert);

                return list.join(" ");
            case 'delete':
                if (tokens.length !== 1 || !Number.isInteger(Number.parseInt(tokens[0]))){
                    throw new Error('Error: invalid command parameters');
                }

                 index = Number(tokens[0]);

                if (index < 0 || index >= list.length) {
                    throw new Error(`Error: invalid index ${index}`);
                }

                list.splice(index, 1);

                return list.join(" ");
            case 'roll':
                if(tokens.length !== 1 || !(tokens[0] === 'left' || tokens[0] === 'right')){
                    throw new Error('Error: invalid command parameters');
                }
                if (tokens[0] === 'left') {
                    let firstItem = list.shift();
                    list.push(firstItem);

                    return list.join(" ");
                } else if (tokens[0] === 'right'){
                    let lastItem = list.pop();
                    list.unshift(lastItem);

                    return list.join(" ");
                }

                break;
            case 'sort':
                if(tokens.length !== 0){
                    throw new Error('Error: invalid command parameters');
                }

                list = list.sort(function(a,b){
                    return a.localeCompare(b);
                });

                return list.join(" ");
            case 'count':
                if(tokens.length !== 1 || tokens[0] === ''){
                    throw new Error('Error: invalid command parameters');
                }

                let counter = 0;
                for (let item of list) {
                    if (item === tokens[0]){
                        counter++;
                    }
                }

                return counter;
            case 'end':
                if (tokens.length !== 0){
                    throw new Error('Error: invalid command parameters');
                }

                return 'Finished';
            default:
                throw new Error('Error: invalid command');
        }
    }
}

listProcessor([
    'alpha beta gamma',
    'append delta',
    'prepend 00',
    'invalid command',
    'insert 1 ins',
    'insert -1 ins',
    'insert 1',
    'delete 3',
    'delete invalid',
    'delete 5',
    'roll left',
    'roll',
    'roll right',
    'just roll',
    'reverse',
    'sort',
    'sort 3',
    'count alpha',
    'append alpha',
    'count alpha',
    'count ALPHA',
    'end 1',
    'END',
    'end'
]);<|endoftext|>


<commit_before>/*
 * NAME: nodejs-demo.js
 * AUTH: Brent Ely (https://github.com/gitbrent/)
 * DESC: Demonstrate SpRestLib on Node.js
 * REQS: Node 4.x + `npm install sprestlib`
 * EXEC: `node nodejs-demo.js (sp-username) (sp-password) {sp-hostUrl}`
 * VER.: 1.4.0
 * REL.: 20180108
 * REFS: HOWTO: Authenticate to SharePoint Online (*.sharepoint.com)
 * - https://allthatjs.com/2012/03/28/remote-authentication-in-sharepoint-online/
 * - http://paulryan.com.au/2014/spo-remote-authentication-rest/
 * - https://github.com/s-KaiNet/node-spoauth
*/

// Required Args
// =============
if (process.argv.length < 5) {
	console.log("*ERROR*: Not enough arguments provided\n");
	console.log("Usage....: node nodejs-demo.js [spUsername] [spPassword] [spHostUrl]");
	console.log("Example..: node nodejs-demo.js admin@billg.onmicrosoft.com c@ashm0ney https://billg.sharepoint.com");
	process.exit(-1);
}

// SETUP: Load sprestlib and show version to verify everything loaded correctly
// ============================================================================
var fs = require('fs');
var https = require('https'); // this Library is the basis for the remote auth solution
var sprLib;
if (fs.existsSync('../dist/sprestlib.js')) {
	sprLib = require('../dist/sprestlib.js'); // for LOCAL TESTING
}
else {
	sprLib = require("sprestlib");
}

// Lets go
console.log('\nStarting demo...');
console.log('================================================================================');
console.log(`> SpRestLib version: ${sprLib.version}\n`); // Loaded okay?

// Office365/On-Prem/Hosted Vars
var SP_USER = process.argv[2];
var SP_PASS = process.argv[3];
var SP_URL  = process.argv[4].replace(/\/$/gi,'');
var SP_HOST = SP_URL.toLowerCase().replace('https://','').replace('http://','');
var gBinarySecurityToken = "";
var gAuthCookie1 = "";
var gAuthCookie2 = "";

Promise.resolve()
.then(() => {
	// STEP 1: Login to MS with user/pass and get SecurityToken
	console.log(' * STEP 1/2: Auth into login.microsoftonline.com ...');

	return new Promise(function(resolve,reject) {
		var xmlRequest = '<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">\n'
			+ '  <s:Header>'
			+ '    <a:Action s:mustUnderstand="1">http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue</a:Action>'
			+ '    <a:ReplyTo><a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address></a:ReplyTo>'
			+ '    <a:To s:mustUnderstand="1">https://login.microsoftonline.com/extSTS.srf</a:To>'
			+ '    <o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">'
			+ '      <o:UsernameToken>'
			+ '        <o:Username>'+ SP_USER +'</o:Username>'
			+ '        <o:Password>'+ SP_PASS +'</o:Password>'
			+ '      </o:UsernameToken>'
			+ '    </o:Security>'
			+ '  </s:Header>'
			+ '  <s:Body>'
			+ '    <t:RequestSecurityToken xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust">'
			+ '      <wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">'
			+ '        <a:EndpointReference><a:Address>'+ SP_URL +'</a:Address></a:EndpointReference>'
			+ '      </wsp:AppliesTo>'
			+ '      <t:KeyType>http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey</t:KeyType>'
			+ '      <t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType>'
			+ '      <t:TokenType>urn:oasis:names:tc:SAML:1.0:assertion</t:TokenType>'
			+ '    </t:RequestSecurityToken>'
			+ '  </s:Body>'
			+ '</s:Envelope>';

		var options = {
			hostname: 'login.microsoftonline.com',
			path    : "/extSTS.srf",
			method  : 'POST',
			headers: {
				'Content-Type': 'application/x-www-form-urlencoded',
				'Content-Length': xmlRequest.length
			}
		};

		var request = https.request(options, (res) => {
			let rawData = '';
			res.setEncoding('utf8');
			res.on('data', (chunk) => rawData += chunk);
			res.on('end', () => {
				var DOMParser = require('xmldom').DOMParser;
				var doc = new DOMParser().parseFromString(rawData, "text/xml");
				// KEY 1: Get SecurityToken
				if ( doc.documentElement.getElementsByTagName('wsse:BinarySecurityToken').item(0) ) {
					gBinarySecurityToken = doc.documentElement.getElementsByTagName('wsse:BinarySecurityToken').item(0).firstChild.nodeValue;
					resolve();
				}
				else {
					reject('Invalid Username/Password');
				}
			});
		});
		request.on('error', (e) => {
			console.log(`problem with request: ${e.message}`);
			reject();
		});
		request.write(xmlRequest);
		request.end();
	});
})
.then(() => {
	// STEP 2: Provide SecurityToken to SP site and get 2 Auth Cookies
	console.log(' * STEP 2/2: Auth into SharePoint ...');

	return new Promise(function(resolve,reject) {
		var options = {
			hostname: SP_HOST,
			agent: false,
			path: "/_forms/default.aspx?wa=wsignin1.0",
			method: 'POST',
			headers: {
				'Content-Type': 'application/x-www-form-urlencoded',
				'Content-Length': gBinarySecurityToken.length,
				'Host': SP_HOST
			}
		};

		// IMPORTANT: SharePoint online will only return the 2 auth cookies with https queries (it will respond to http, but not include cookies!)
		var request = https.request(options, (response) => {
			// KEY 2: Get 2 auth cookie values
			gAuthCookie1 = response.headers['set-cookie'][0].substring(0,response.headers['set-cookie'][0].indexOf(';'));
			gAuthCookie2 = response.headers['set-cookie'][1].substring(0,response.headers['set-cookie'][1].indexOf(';'));
			resolve();
		});
		request.on('error', (e) => {
			console.log(`problem with request: ${e.message}`);
			reject(e);
		});
		request.write(gBinarySecurityToken);
		request.end();
	});
})
.then((data) => {
	// STEP 3: Send requests including authentication cookies
	console.log(' * SUCCESS!! Authenticated into "'+ SP_HOST +'"');
	//console.log(`...gAuthCookie1:\n${gAuthCookie1}\n`);
	//console.log(`...gAuthCookie2:\n${gAuthCookie2}\n`);

	// A: SpRestLib requires 2 things: auth-cookie & server-name
	sprLib.nodeConfig({ cookie:gAuthCookie1+' ;'+gAuthCookie2, server:SP_HOST });

	// B: SpRestLib also needs the full path to your site
	sprLib.baseUrl('/sites/dev/');
	//console.log( 'sprLib.baseUrl = '+ sprLib.baseUrl() );

	// C: Now run all the sprLib API calls you want
	return sprLib.user().info();
})
.then((objUser) => {
	console.log('\nTEST 1: sprLib.user().info()');
	console.log('----------------------------');
	//console.log(objUser);
	console.log('Id.........: '+ objUser.Id);
	console.log('Title......: '+ objUser.Title);
	console.log('LoginName..: '+ objUser.LoginName);
	console.log('Email......: '+ objUser.Email);

	return sprLib.list('Site Assets').info();
})
.then((objInfo) => {
	console.log("\nTEST 2: sprLib.list('Site Assets').info()");
	console.log('-----------------------------------------');
	console.log('Created....: '+ objInfo.Created);
	console.log('ItemCount..: '+ objInfo.ItemCount);

	// CRUD Test:
	//sprLib.list('Departments').create({ Title:'node test' });
	// THIS WILL FAIL - "The security validation for this page is invalid and might be corrupted. Please use your web browser's Back button to try your operation again."
	// a `requestDigest` must be generated and included
	return sprLib.rest({ url:'_api/contextinfo', type:'POST' });
})
.then(arr => {
	let strReqDig = arr[0].GetContextWebInformation.FormDigestValue;

	console.log("\nTEST 3: sprLib.list('Announcements').create()");
	console.log('---------------------------------------------');
	console.log('strReqDig..:'+ strReqDig);

	return sprLib.list({ name:'Announcements', requestDigest:strReqDig }).create({ "Title":"created with Node demo" });
})
.then((objCrud) => {
	console.log('..\n..create done!');
	console.log('New item ID...: '+ objCrud.ID);

	// NEGATIVE-TEST: (check for error msg response)
	//return sprLib.list('Site Assets').getItems({ listCols:['ColDoesntExist'] });
})
.then(() => {
	console.log('\n================================================================================');
	console.log('...demo complete.\n');
})
.catch((strErr) => {
	console.error('\n!!! ERROR !!!');
	console.error(strErr);
	return;
});
<commit_msg>updated version<commit_after>/*
 * NAME: nodejs-demo.js
 * AUTH: Brent Ely (https://github.com/gitbrent/)
 * DESC: Demonstrate SpRestLib on Node.js
 * REQS: Node 4.x + `npm install sprestlib`
 * EXEC: `node nodejs-demo.js (sp-username) (sp-password) {sp-hostUrl}`
 * VER.: 1.5.0
 * REL.: 20180301
 * REFS: HOWTO: Authenticate to SharePoint Online (*.sharepoint.com)
 * - https://allthatjs.com/2012/03/28/remote-authentication-in-sharepoint-online/
 * - http://paulryan.com.au/2014/spo-remote-authentication-rest/
 * - https://github.com/s-KaiNet/node-spoauth
*/

// Required Args
// =============
if (process.argv.length < 5) {
	console.log("*ERROR*: Not enough arguments provided\n");
	console.log("Usage....: node nodejs-demo.js [spUsername] [spPassword] [spHostUrl]");
	console.log("Example..: node nodejs-demo.js admin@billg.onmicrosoft.com c@ashm0ney https://billg.sharepoint.com");
	process.exit(-1);
}

// SETUP: Load sprestlib and show version to verify everything loaded correctly
// ============================================================================
var fs = require('fs');
var https = require('https'); // this Library is the basis for the remote auth solution
var sprLib;
if (fs.existsSync('../dist/sprestlib.js')) {
	sprLib = require('../dist/sprestlib.js'); // for LOCAL TESTING
}
else {
	sprLib = require("sprestlib");
}

// Lets go
console.log('\nStarting demo...');
console.log('================================================================================');
console.log(`> SpRestLib version: ${sprLib.version}\n`); // Loaded okay?

// Office365/On-Prem/Hosted Vars
var SP_USER = process.argv[2];
var SP_PASS = process.argv[3];
var SP_URL  = process.argv[4].replace(/\/$/gi,'');
var SP_HOST = SP_URL.toLowerCase().replace('https://','').replace('http://','');
var gBinarySecurityToken = "";
var gAuthCookie1 = "";
var gAuthCookie2 = "";

Promise.resolve()
.then(() => {
	// STEP 1: Login to MS with user/pass and get SecurityToken
	console.log(' * STEP 1/2: Auth into login.microsoftonline.com ...');

	return new Promise(function(resolve,reject) {
		var xmlRequest = '<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">\n'
			+ '  <s:Header>'
			+ '    <a:Action s:mustUnderstand="1">http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue</a:Action>'
			+ '    <a:ReplyTo><a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address></a:ReplyTo>'
			+ '    <a:To s:mustUnderstand="1">https://login.microsoftonline.com/extSTS.srf</a:To>'
			+ '    <o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">'
			+ '      <o:UsernameToken>'
			+ '        <o:Username>'+ SP_USER +'</o:Username>'
			+ '        <o:Password>'+ SP_PASS +'</o:Password>'
			+ '      </o:UsernameToken>'
			+ '    </o:Security>'
			+ '  </s:Header>'
			+ '  <s:Body>'
			+ '    <t:RequestSecurityToken xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust">'
			+ '      <wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">'
			+ '        <a:EndpointReference><a:Address>'+ SP_URL +'</a:Address></a:EndpointReference>'
			+ '      </wsp:AppliesTo>'
			+ '      <t:KeyType>http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey</t:KeyType>'
			+ '      <t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType>'
			+ '      <t:TokenType>urn:oasis:names:tc:SAML:1.0:assertion</t:TokenType>'
			+ '    </t:RequestSecurityToken>'
			+ '  </s:Body>'
			+ '</s:Envelope>';

		var options = {
			hostname: 'login.microsoftonline.com',
			path    : "/extSTS.srf",
			method  : 'POST',
			headers: {
				'Content-Type': 'application/x-www-form-urlencoded',
				'Content-Length': xmlRequest.length
			}
		};

		var request = https.request(options, (res) => {
			let rawData = '';
			res.setEncoding('utf8');
			res.on('data', (chunk) => rawData += chunk);
			res.on('end', () => {
				var DOMParser = require('xmldom').DOMParser;
				var doc = new DOMParser().parseFromString(rawData, "text/xml");
				// KEY 1: Get SecurityToken
				if ( doc.documentElement.getElementsByTagName('wsse:BinarySecurityToken').item(0) ) {
					gBinarySecurityToken = doc.documentElement.getElementsByTagName('wsse:BinarySecurityToken').item(0).firstChild.nodeValue;
					resolve();
				}
				else {
					reject('Invalid Username/Password');
				}
			});
		});
		request.on('error', (e) => {
			console.log(`problem with request: ${e.message}`);
			reject();
		});
		request.write(xmlRequest);
		request.end();
	});
})
.then(() => {
	// STEP 2: Provide SecurityToken to SP site and get 2 Auth Cookies
	console.log(' * STEP 2/2: Auth into SharePoint ...');

	return new Promise(function(resolve,reject) {
		var options = {
			hostname: SP_HOST,
			agent: false,
			path: "/_forms/default.aspx?wa=wsignin1.0",
			method: 'POST',
			headers: {
				'Content-Type': 'application/x-www-form-urlencoded',
				'Content-Length': gBinarySecurityToken.length,
				'Host': SP_HOST
			}
		};

		// IMPORTANT: SharePoint online will only return the 2 auth cookies with https queries (it will respond to http, but not include cookies!)
		var request = https.request(options, (response) => {
			// KEY 2: Get 2 auth cookie values
			gAuthCookie1 = response.headers['set-cookie'][0].substring(0,response.headers['set-cookie'][0].indexOf(';'));
			gAuthCookie2 = response.headers['set-cookie'][1].substring(0,response.headers['set-cookie'][1].indexOf(';'));
			resolve();
		});
		request.on('error', (e) => {
			console.log(`problem with request: ${e.message}`);
			reject(e);
		});
		request.write(gBinarySecurityToken);
		request.end();
	});
})
.then((data) => {
	// STEP 3: Send requests including authentication cookies
	console.log(' * SUCCESS!! Authenticated into "'+ SP_HOST +'"');
	//console.log(`...gAuthCookie1:\n${gAuthCookie1}\n`);
	//console.log(`...gAuthCookie2:\n${gAuthCookie2}\n`);

	// A: SpRestLib requires 2 things: auth-cookie & server-name
	sprLib.nodeConfig({ cookie:gAuthCookie1+' ;'+gAuthCookie2, server:SP_HOST });

	// B: SpRestLib also needs the full path to your site
	sprLib.baseUrl('/sites/dev/');
	//console.log( 'sprLib.baseUrl = '+ sprLib.baseUrl() );

	// C: Now run all the sprLib API calls you want
	return sprLib.user().info();
})
.then((objUser) => {
	console.log('\nTEST 1: sprLib.user().info()');
	console.log('----------------------------');
	//console.log(objUser);
	console.log('Id.........: '+ objUser.Id);
	console.log('Title......: '+ objUser.Title);
	console.log('LoginName..: '+ objUser.LoginName);
	console.log('Email......: '+ objUser.Email);

	return sprLib.list('Site Assets').info();
})
.then((objInfo) => {
	console.log("\nTEST 2: sprLib.list('Site Assets').info()");
	console.log('-----------------------------------------');
	console.log('Created....: '+ objInfo.Created);
	console.log('ItemCount..: '+ objInfo.ItemCount);

	// CRUD Test:
	//sprLib.list('Departments').create({ Title:'node test' });
	// THIS WILL FAIL - "The security validation for this page is invalid and might be corrupted. Please use your web browser's Back button to try your operation again."
	// a `requestDigest` must be generated and included
	return sprLib.rest({ url:'_api/contextinfo', type:'POST' });
})
.then(arr => {
	let strReqDig = arr[0].GetContextWebInformation.FormDigestValue;

	console.log("\nTEST 3: sprLib.list('Announcements').create()");
	console.log('---------------------------------------------');
	console.log('strReqDig..:'+ strReqDig);

	return sprLib.list({ name:'Announcements', requestDigest:strReqDig }).create({ "Title":"created with Node demo" });
})
.then((objCrud) => {
	console.log('..\n..create done!');
	console.log('New item ID...: '+ objCrud.ID);

	// NEGATIVE-TEST: (check for error msg response)
	//return sprLib.list('Site Assets').getItems({ listCols:['ColDoesntExist'] });
})
.then(() => {
	console.log('\n================================================================================');
	console.log('...demo complete.\n');
})
.catch((strErr) => {
	console.error('\n!!! ERROR !!!');
	console.error(strErr);
	return;
});
<|endoftext|>


<commit_before>/*jslint indent:2*/
'use strict';

module.exports = function (object) {

  var objectList = [],
    stack = [ object ],
    bytes = 0,
    value,
    i;

  while (stack.length) {
    value = stack.pop();

    if (typeof value === 'boolean') {
      bytes += 4;
    } else if (typeof value === 'string') {
      bytes += value.length * 2;
    } else if (typeof value === 'number') {
      bytes += 8;
    } else if (typeof value === 'object'
        && objectList.indexOf(value) === -1) {
      objectList.push(value);

      for (i in value) {
        if (value.hasOwnProperty(i)) {
          stack.push(value[i]);
        }
      }
    }
  }
  return bytes;
};<commit_msg>Remove from utils and added as a subtree from a Git Gist.<commit_after><|endoftext|>


<commit_before><commit_msg>Add NCSA Spelling<commit_after><|endoftext|>


<commit_before>(function() {
  /* global SocketFetchOptions */
  if (this.currentImport && this.currentImport.URL) {
    var url = this.currentImport.URL;
    var path = url.substr(url.indexOf('/', url.indexOf('/') + 2)).replace('socket-fetch.html','');
    var lastDot = path.lastIndexOf('.');
    if (lastDot !== -1 && path.substr(lastDot + 1) === 'html') {
      path = path.substr(path.lastIndexOf('/'));
    }
    path += '%s';
    SocketFetchOptions.importUrl = path;
  }
}).call(window);
<commit_msg>Update include path<commit_after>(function() {
  /* global SocketFetchOptions */
  if (this.currentImport && this.currentImport.URL) {
    var url = this.currentImport.URL;
    var path = url.substr(url.indexOf('/', url.indexOf('/') + 2)).replace('socket-fetch.html','');
    var lastDot = path.lastIndexOf('.');
    if (lastDot !== -1 && path.substr(lastDot + 1) === 'html') {
      // remove element file name path
      path = path.substr(0, path.lastIndexOf('/') + 1);
    }
    path += '%s';
    SocketFetchOptions.importUrl = path;
  }
}).call(window);
<|endoftext|>


<commit_before>dojo.provide("dojox.mobile.deviceTheme");

// summary:
//		Detects the User Agent of the browser and loads appropriate theme files.
//		Simply dojo.require this module to enable the automatic theme loading.
//
//	example:
//			dojo.require("dojox.mobile");
//			dojo.require("dojox.mobile.deviceTheme");

dojox.mobile.loadCssFile = function(/*String*/file){
	dojo.create("LINK", {
		href: file,
		type: "text/css",
		rel: "stylesheet"
	}, dojo.doc.getElementsByTagName('head')[0]);
};

// A map of user-agents to theme files.
// The first array element is a regexp pattern that matches the userAgent string.
// The second array element is an array of css file paths.
// The matching is performed in the array order, and stops after the first match.
dojox.mobile.themeMap = [
	[
		"Android",
		[dojo.moduleUrl("dojox.mobile", "themes/android/android.css")]
	],
	[
		"BlackBerry",
		[dojo.moduleUrl("dojox.mobile", "themes/blackberry/blackberry.css")]
	],
	[
		"iPad",
		[dojo.moduleUrl("dojox.mobile", "themes/iphone/iphone.css"),
		 dojo.moduleUrl("dojox.mobile", "themes/iphone/ipad.css")]
	],
	[
		".*",
		[dojo.moduleUrl("dojox.mobile", "themes/iphone/iphone.css")]
	]
];

dojox.mobile.loadDeviceTheme = function(){
	var m = dojox.mobile.themeMap;
	for(var i = 0; i < m.length; i++){
		var re1 = new RegExp("theme="+m[i][0]);
		var re2 = new RegExp(m[i][0]);
		if(location.search.match(re1) || navigator.userAgent.match(re2)){
			var files = m[i][1];
			for(var j = 0; j < files.length; j++){
				dojox.mobile.loadCssFile(files[j]);
			}
			break;
		}
	}
};

dojox.mobile.loadDeviceTheme();
<commit_msg>Fixes #12638 !strict. Allowed the user to choose what theme files to load.<commit_after>dojo.provide("dojox.mobile.deviceTheme");

// summary:
//		Automatic Theme Loader
// description:
//		Detects the User Agent of the browser and loads appropriate theme files.
//		Simply dojo.require this module to enable the automatic theme loading.
//
//		By default, an all-in-one theme file (e.g. themes/iphone/iphone.css) is
//		loaded. The all-in-one theme files contain style sheets for all the
//		dojox.mobile widgets regardless of whether they are used in your
//		application or not.
//		If you want to choose what theme files to load, you can specify them
//		via djConfig as shown in the following example:
//
//	|	djConfig="parseOnLoad:true, mblThemeFiles:['base','Button']"
//
//		Or you may want to use dojox.mobile.themeFiles as follows to get the
//		same result. Note that the assignment has to be done before loading
//		deviceTheme.js.
//
//	|	dojo.require("dojox.mobile");
//	|	dojox.mobile.themeFiles = ['base','Button'];
//	|	dojo.require("dojox.mobile.deviceTheme");
//
//		In the case of this example, if iphone is detected, for example, the
//		following files will be loaded:
//
//	|	dojox/mobile/themes/iphone/base.css
//	|	dojox/mobile/themes/iphone/Button.css
//
//		If you want to load style sheets for your own custom widgets, you can
//		specify a package name along with a theme file name in an array.
//
//	|	['base',['com.acme','MyWidget']]
//
//		In this case, the following files will be loaded.
//
//	|	dojox/mobile/themes/iphone/base.css
//	|	com/acme/themes/iphone/MyWidget.css
//
//		If you specify '@theme' as a theme file name, it will be replaced with
//		the theme folder name. For example,
//
//	|	['@theme',['com.acme','MyWidget']]
//
//		will load the following files.
//
//	|	dojox/mobile/themes/iphone/iphone.css
//	|	com/acme/themes/iphone/MyWidget.css

dojox.mobile.loadCssFile = function(/*String*/file){
	dojo.create("LINK", {
		href: file,
		type: "text/css",
		rel: "stylesheet"
	}, dojo.doc.getElementsByTagName('head')[0]);
};

dojox.mobile.themeMap = [
	// summary:
	//		A map of user-agents to theme files.
	// description:
	//		The first array element is a regexp pattern that matches the
	//		userAgent string.
	//
	//		The second array element is a theme folder name.
	//
	//		The third array element is an array of css file paths to load.
	//
	//		The matching is performed in the array order, and stops after the
	//		first match.
	[
		"Android",
		"android",
		[]
	],
	[
		"BlackBerry",
		"blackberry",
		[]
	],
	[
		"iPad",
		"iphone",
		[dojo.moduleUrl("dojox.mobile", "themes/iphone/ipad.css")]
	],
	[
		".*",
		"iphone",
		[]
	]
];

dojox.mobile.loadDeviceTheme = function(){
	var t = dojo.config["mblThemeFiles"] || dojox.mobile.themeFiles || ["@theme"];
	if(!dojo.isArray(t)){ alert("loadDeviceTheme: array is expected but found: "+t); }
	var i, j;
	var m = dojox.mobile.themeMap;
	for(i = 0; i < m.length; i++){
		var re1 = new RegExp("theme="+m[i][0]);
		var re2 = new RegExp(m[i][0]);
		if(location.search.match(re1) || navigator.userAgent.match(re2)){
			var theme = m[i][1];
			var files = m[i][2];
			for(j = t.length - 1; j >= 0; j--){
				var pkg = dojo.isArray(t[j]) ? t[j][0] : "dojox.mobile";
				var name = dojo.isArray(t[j]) ? t[j][1] : t[j];
				var f = "themes/" + theme + "/" +
					(name === "@theme" ? theme : name) + ".css";
				files.unshift(dojo.moduleUrl(pkg, f));
			}
			for(j = 0; j < files.length; j++){
				dojox.mobile.loadCssFile(files[j]);
			}
			break;
		}
	}
};

dojox.mobile.loadDeviceTheme();
<|endoftext|>


<commit_before>/**
 * @author Pedro Sanders
 * @since v1
 */
load('mod/core/context.js')
load('mod/utils/auth_helper.js')
load('mod/utils/acl_helper.js')
load('mod/utils/domain_utils.js')
load('mod/core/resources.js')

function Processor(sipProvider, headerFactory, messageFactory, addressFactory, contactHeader, locationService,
    registrarService, accountManagerService, resourcesAPI, contextStorage, config) {
    const SipListener = Packages.javax.sip.SipListener
    const Request = Packages.javax.sip.message.Request
    const Response = Packages.javax.sip.message.Response
    const RouteHeader = Packages.javax.sip.header.RouteHeader
    const ToHeader = Packages.javax.sip.header.ToHeader
    const FromHeader = Packages.javax.sip.header.FromHeader
    const ContactHeader = Packages.javax.sip.header.ContactHeader
    const ExpiresHeader = Packages.javax.sip.header.ExpiresHeader
    const ViaHeader = Packages.javax.sip.header.ViaHeader
    const CSeqHeader = Packages.javax.sip.header.CSeqHeader
    const AuthorizationHeader = Packages.javax.sip.header.AuthorizationHeader
    const ProxyAuthorizationHeader = Packages.javax.sip.header.ProxyAuthorizationHeader
    const LogManager = Packages.org.apache.logging.log4j.LogManager

    const LOG = LogManager.getLogger()
    const authHelper =  new AuthHelper(headerFactory)

    const defaultDomainAcl = config.defaultDomainAcl

    function register(request, transaction) {
        const contactHeader = request.getHeader(ContactHeader.NAME)
        const contactURI = contactHeader.getAddress().getURI()
        const expH = request.getHeader(ExpiresHeader.NAME) || contactHeader.getExpires()
        const authHeader = request.getHeader(AuthorizationHeader.NAME)

        if (authHeader == null) {
            const unauthorized = messageFactory.createResponse(Response.UNAUTHORIZED, request)
            unauthorized.addHeader(authHelper.generateChallenge())
            transaction.sendResponse(unauthorized)
            LOG.trace(unauthorized)
        } else {
            if (registrarService.register(request)) {
                const ok = messageFactory.createResponse(Response.OK, request)
                ok.addHeader(contactHeader)
                ok.addHeader(expH)
                transaction.sendResponse(ok)
                LOG.trace("\n" + ok)
            } else {
                const unauthorized = messageFactory.createResponse(Response.UNAUTHORIZED, request)
                unauthorized.addHeader(authHelper.generateChallenge(headerFactory))
                transaction.sendResponse(unauthorized)
                LOG.trace(unauthorized)
            }
        }
    }

    function cancel(request, serverTransaction) {
        const storage = contextStorage.getStorage()
        const iterator = storage.iterator()

        while (iterator.hasNext()) {
            const context = iterator.next()
            if (context.serverTransaction && context.serverTransaction.getBranchId()
                .equals(serverTransaction.getBranchId())) {

                let originRequest = context.requestIn
                let originResponse = messageFactory.createResponse(Response.REQUEST_TERMINATED, originRequest)
                let cancelResponse = messageFactory.createResponse(Response.OK, request)
                let cancelRequest = context.clientTransaction.createCancel()
                let clientTransaction = sipProvider.getNewClientTransaction(cancelRequest)

                context.serverTransaction.sendResponse(originResponse)
                serverTransaction.sendResponse(cancelResponse)
                clientTransaction.sendRequest()

                LOG.trace('Original response: ' + originResponse)
                LOG.trace('Cancel response: ' + cancelResponse)
                LOG.trace('Cancel request: ' + cancelRequest)
            }
        }
    }

    function unavailable(request, transaction) {
        LOG.debug("Unable to find contact: " + request.getHeader(ToHeader.NAME))
        transaction.sendResponse(messageFactory.createResponse(Response.NOT_FOUND, request))
    }

    function reject(request, transaction) {
        LOG.debug("Connection rejected: " + request)
        transaction.sendResponse(messageFactory.createResponse(Response.UNAUTHORIZED, request))
    }

    this.listener = new SipListener() {
        processRequest: event => {
            const requestIn = event.getRequest()
            const method = requestIn.getMethod()
            const routeHeader = requestIn.getHeader(RouteHeader.NAME)
            const toHeader = requestIn.getHeader(ToHeader.NAME)
            const requestVia = requestIn.getHeader(ViaHeader.NAME)
            const tgtURI = toHeader.getAddress().getURI()

            let serverTransaction = event.getServerTransaction()
            let proxyHost

            // Edge proxy
            if (routeHeader == null) {
                proxyHost = config.ip;
            } else {
                const sipURI = routeHeader.getAddress().getURI()
                proxyHost = sipURI.getHost()
            }

            if (serverTransaction == null && !method.equals(Request.ACK)) {
                serverTransaction = sipProvider.getNewServerTransaction(requestIn)
            }

            const domain = resourcesAPI.findDomain(tgtURI.getHost())

            if (domain != null) {
                if(!new DomainUtil(defaultDomainAcl).isDomainAllow(domain, tgtURI.getHost())) {
                    reject(requestIn, serverTransaction)
                }
            }

            const requestOut = requestIn.clone()

            if (method.equals(Request.REGISTER)) {
                register(requestIn, serverTransaction)
            } else if(method.equals(Request.CANCEL)) {
                cancel(requestIn, serverTransaction)
            } else {
                // Last proxy in route
                if (proxyHost.equals(config.ip) || proxyHost.equals(config.externalIp)) {
                    const transport = requestVia.getTransport().toLowerCase()
                    const port = sipProvider.getListeningPoint(transport).getPort()
                    const viaHeader = headerFactory.createViaHeader(proxyHost, port, transport, null)

                    // Why do I remove this header here?
                    requestOut.removeFirst(RouteHeader.NAME)
                    requestOut.addFirst(viaHeader)
                }

                const tgt = tgtURI.getScheme() + ":" + tgtURI.getUser() + '@' + tgtURI.getHost()
                const uri = locationService.get(tgt)

                if (uri == null) {
                    unavailable(requestIn, serverTransaction)
                    return
                }

                requestOut.setRequestURI(uri)

                // Does not need a transaction
                if(method.equals(Request.ACK)) {
                    sipProvider.sendRequest(requestOut)
                } else {
                    try {
                        const clientTransaction = sipProvider.getNewClientTransaction(requestOut)
                        clientTransaction.sendRequest()

                        // Transaction context
                        const context = new Context()
                        context.clientTransaction = clientTransaction
                        context.serverTransaction = serverTransaction
                        context.method = method
                        context.requestIn = requestIn
                        context.requestOut = requestOut
                        contextStorage.saveContext(context)
                    } catch (e) {
                        LOG.info(e.getMessage())
                        LOG.trace(e.getStackTrace())
                    }
                }
            }
            LOG.trace(requestOut)
        },

        processResponse: event => {
            const responseIn = event.getResponse()
            const cseq = responseIn.getHeader(CSeqHeader.NAME)
            const fromHeader = responseIn.getHeader(FromHeader.NAME)

            if (responseIn.getStatusCode() == Response.TRYING ||
                responseIn.getStatusCode() == Response.REQUEST_TERMINATED) return
            if (cseq.getMethod().equals(Request.CANCEL)) return

            const clientTransaction = event.getClientTransaction()

            // WARNING: This is causing an issue with TCP transport and DIDLogic
            // I suspect that DIDLogic does not fully support tcp registration
            if (responseIn.getStatusCode() == Response.PROXY_AUTHENTICATION_REQUIRED ||
                responseIn.getStatusCode() == Response.UNAUTHORIZED) {

                let authenticationHelper = sipProvider.getSipStack()
                    .getAuthenticationHelper(accountManagerService.getAccountManager(), headerFactory)

                let t = authenticationHelper.handleChallenge(responseIn, clientTransaction, event.getSource(), 5)
                t.sendRequest()
                return
            }

            // Strip the topmost via header
            const responseOut = responseIn.clone();
            responseOut.removeFirst(ViaHeader.NAME);

            let fromAPI = false
            if (fromHeader.getParameter('origin') != null) fromAPI = true

            if (cseq.getMethod().equals(Request.INVITE) && responseIn.getStatusCode() == Response.OK && fromAPI) {
                LOG.debug('Call originated from API')
                const dialog = clientTransaction.getDialog()
                const ackRequest = dialog.createAck(cseq.getSequenceNumber())
                dialog.sendAck(ackRequest)
            } else if (cseq.getMethod().equals(Request.INVITE)) {
                if (clientTransaction != null) {
                    // In theory we should be able to obtain the ServerTransaction casting the ApplicationData.
                    // However, I'm unable to find the way to cast this object.
                    //let st = clientTransaction.getApplicationData()'

                    const context = contextStorage.findContext(clientTransaction)
                    // serverTransaction will be undefined when using the Originate functionality
                    if (context.serverTransaction) context.serverTransaction.sendResponse(responseOut)
                } else {
                    // Client tx has already terminated but the UA is retransmitting
                    // just forward the response statelessly.
                    // Send the retransmission statelessly
                    if (responseIn.getHeader(ViaHeader.NAME) != null) sipProvider.sendResponse(responseOut);
                }
            } else {
                // Can be BYE due to Record-Route
                LOG.trace('Got a non-invite response ' + responseOut);
                // There is no more Via headers; the response was intended for the proxy.
                if (responseOut.getHeader(ViaHeader.NAME) != null) sipProvider.sendResponse(responseOut);
            }
            LOG.trace(responseOut)
        },

        processTransactionTerminated: event => {
            if (event.isServerTransaction()) {
                const serverTransaction = event.getServerTransaction()

                if (!contextStorage.removeContext(serverTransaction)) {
                   LOG.info("Ongoing Transaction")
                }
            }
        },

        processDialogTerminated: event => {
            LOG.info('Dialog ' + event.getDialog() + ' has been terminated')
        },

        processTimeout: event => {
            LOG.info('Transaction Time out')
        }
    }
}
<commit_msg>Downgrading several messages from INFO to DEBUG<commit_after>/**
 * @author Pedro Sanders
 * @since v1
 */
load('mod/core/context.js')
load('mod/utils/auth_helper.js')
load('mod/utils/acl_helper.js')
load('mod/utils/domain_utils.js')
load('mod/core/resources.js')

function Processor(sipProvider, headerFactory, messageFactory, addressFactory, contactHeader, locationService,
    registrarService, accountManagerService, resourcesAPI, contextStorage, config) {
    const SipListener = Packages.javax.sip.SipListener
    const Request = Packages.javax.sip.message.Request
    const Response = Packages.javax.sip.message.Response
    const RouteHeader = Packages.javax.sip.header.RouteHeader
    const ToHeader = Packages.javax.sip.header.ToHeader
    const FromHeader = Packages.javax.sip.header.FromHeader
    const ContactHeader = Packages.javax.sip.header.ContactHeader
    const ExpiresHeader = Packages.javax.sip.header.ExpiresHeader
    const ViaHeader = Packages.javax.sip.header.ViaHeader
    const CSeqHeader = Packages.javax.sip.header.CSeqHeader
    const AuthorizationHeader = Packages.javax.sip.header.AuthorizationHeader
    const ProxyAuthorizationHeader = Packages.javax.sip.header.ProxyAuthorizationHeader
    const LogManager = Packages.org.apache.logging.log4j.LogManager

    const LOG = LogManager.getLogger()
    const authHelper =  new AuthHelper(headerFactory)

    const defaultDomainAcl = config.defaultDomainAcl

    function register(request, transaction) {
        const contactHeader = request.getHeader(ContactHeader.NAME)
        const contactURI = contactHeader.getAddress().getURI()
        const expH = request.getHeader(ExpiresHeader.NAME) || contactHeader.getExpires()
        const authHeader = request.getHeader(AuthorizationHeader.NAME)

        if (authHeader == null) {
            const unauthorized = messageFactory.createResponse(Response.UNAUTHORIZED, request)
            unauthorized.addHeader(authHelper.generateChallenge())
            transaction.sendResponse(unauthorized)
            LOG.trace(unauthorized)
        } else {
            if (registrarService.register(request)) {
                const ok = messageFactory.createResponse(Response.OK, request)
                ok.addHeader(contactHeader)
                ok.addHeader(expH)
                transaction.sendResponse(ok)
                LOG.trace("\n" + ok)
            } else {
                const unauthorized = messageFactory.createResponse(Response.UNAUTHORIZED, request)
                unauthorized.addHeader(authHelper.generateChallenge(headerFactory))
                transaction.sendResponse(unauthorized)
                LOG.trace(unauthorized)
            }
        }
    }

    function cancel(request, serverTransaction) {
        const storage = contextStorage.getStorage()
        const iterator = storage.iterator()

        while (iterator.hasNext()) {
            const context = iterator.next()
            if (context.serverTransaction && context.serverTransaction.getBranchId()
                .equals(serverTransaction.getBranchId())) {

                let originRequest = context.requestIn
                let originResponse = messageFactory.createResponse(Response.REQUEST_TERMINATED, originRequest)
                let cancelResponse = messageFactory.createResponse(Response.OK, request)
                let cancelRequest = context.clientTransaction.createCancel()
                let clientTransaction = sipProvider.getNewClientTransaction(cancelRequest)

                context.serverTransaction.sendResponse(originResponse)
                serverTransaction.sendResponse(cancelResponse)
                clientTransaction.sendRequest()

                LOG.trace('Original response: ' + originResponse)
                LOG.trace('Cancel response: ' + cancelResponse)
                LOG.trace('Cancel request: ' + cancelRequest)
            }
        }
    }

    function unavailable(request, transaction) {
        LOG.debug("Unable to find contact: " + request.getHeader(ToHeader.NAME))
        transaction.sendResponse(messageFactory.createResponse(Response.NOT_FOUND, request))
    }

    function reject(request, transaction) {
        LOG.debug("Connection rejected: " + request)
        transaction.sendResponse(messageFactory.createResponse(Response.UNAUTHORIZED, request))
    }

    this.listener = new SipListener() {
        processRequest: event => {
            const requestIn = event.getRequest()
            const method = requestIn.getMethod()
            const routeHeader = requestIn.getHeader(RouteHeader.NAME)
            const toHeader = requestIn.getHeader(ToHeader.NAME)
            const requestVia = requestIn.getHeader(ViaHeader.NAME)
            const tgtURI = toHeader.getAddress().getURI()

            let serverTransaction = event.getServerTransaction()
            let proxyHost

            // Edge proxy
            if (routeHeader == null) {
                proxyHost = config.ip;
            } else {
                const sipURI = routeHeader.getAddress().getURI()
                proxyHost = sipURI.getHost()
            }

            if (serverTransaction == null && !method.equals(Request.ACK)) {
                serverTransaction = sipProvider.getNewServerTransaction(requestIn)
            }

            const domain = resourcesAPI.findDomain(tgtURI.getHost())

            if (domain != null) {
                if(!new DomainUtil(defaultDomainAcl).isDomainAllow(domain, tgtURI.getHost())) {
                    reject(requestIn, serverTransaction)
                }
            }

            const requestOut = requestIn.clone()

            if (method.equals(Request.REGISTER)) {
                register(requestIn, serverTransaction)
            } else if(method.equals(Request.CANCEL)) {
                cancel(requestIn, serverTransaction)
            } else {
                // Last proxy in route
                if (proxyHost.equals(config.ip) || proxyHost.equals(config.externalIp)) {
                    const transport = requestVia.getTransport().toLowerCase()
                    const port = sipProvider.getListeningPoint(transport).getPort()
                    const viaHeader = headerFactory.createViaHeader(proxyHost, port, transport, null)

                    // Why do I remove this header here?
                    requestOut.removeFirst(RouteHeader.NAME)
                    requestOut.addFirst(viaHeader)
                }

                const tgt = tgtURI.getScheme() + ":" + tgtURI.getUser() + '@' + tgtURI.getHost()
                const uri = locationService.get(tgt)

                if (uri == null) {
                    unavailable(requestIn, serverTransaction)
                    return
                }

                requestOut.setRequestURI(uri)

                // Does not need a transaction
                if(method.equals(Request.ACK)) {
                    sipProvider.sendRequest(requestOut)
                } else {
                    try {
                        const clientTransaction = sipProvider.getNewClientTransaction(requestOut)
                        clientTransaction.sendRequest()

                        // Transaction context
                        const context = new Context()
                        context.clientTransaction = clientTransaction
                        context.serverTransaction = serverTransaction
                        context.method = method
                        context.requestIn = requestIn
                        context.requestOut = requestOut
                        contextStorage.saveContext(context)
                    } catch (e) {
                        LOG.info(e.getMessage())
                        LOG.trace(e.getStackTrace())
                    }
                }
            }
            LOG.trace(requestOut)
        },

        processResponse: event => {
            const responseIn = event.getResponse()
            const cseq = responseIn.getHeader(CSeqHeader.NAME)
            const fromHeader = responseIn.getHeader(FromHeader.NAME)

            if (responseIn.getStatusCode() == Response.TRYING ||
                responseIn.getStatusCode() == Response.REQUEST_TERMINATED) return
            if (cseq.getMethod().equals(Request.CANCEL)) return

            const clientTransaction = event.getClientTransaction()

            // WARNING: This is causing an issue with TCP transport and DIDLogic
            // I suspect that DIDLogic does not fully support tcp registration
            if (responseIn.getStatusCode() == Response.PROXY_AUTHENTICATION_REQUIRED ||
                responseIn.getStatusCode() == Response.UNAUTHORIZED) {

                let authenticationHelper = sipProvider.getSipStack()
                    .getAuthenticationHelper(accountManagerService.getAccountManager(), headerFactory)

                let t = authenticationHelper.handleChallenge(responseIn, clientTransaction, event.getSource(), 5)
                t.sendRequest()
                return
            }

            // Strip the topmost via header
            const responseOut = responseIn.clone();
            responseOut.removeFirst(ViaHeader.NAME);

            let fromAPI = false
            if (fromHeader.getParameter('origin') != null) fromAPI = true

            if (cseq.getMethod().equals(Request.INVITE) && responseIn.getStatusCode() == Response.OK && fromAPI) {
                LOG.debug('Call originated from API')
                const dialog = clientTransaction.getDialog()
                const ackRequest = dialog.createAck(cseq.getSequenceNumber())
                dialog.sendAck(ackRequest)
            } else if (cseq.getMethod().equals(Request.INVITE)) {
                if (clientTransaction != null) {
                    // In theory we should be able to obtain the ServerTransaction casting the ApplicationData.
                    // However, I'm unable to find the way to cast this object.
                    //let st = clientTransaction.getApplicationData()'

                    const context = contextStorage.findContext(clientTransaction)
                    // serverTransaction will be undefined when using the Originate functionality
                    if (context.serverTransaction) context.serverTransaction.sendResponse(responseOut)
                } else {
                    // Client tx has already terminated but the UA is retransmitting
                    // just forward the response statelessly.
                    // Send the retransmission statelessly
                    if (responseIn.getHeader(ViaHeader.NAME) != null) sipProvider.sendResponse(responseOut);
                }
            } else {
                // Can be BYE due to Record-Route
                LOG.trace('Got a non-invite response ' + responseOut);
                // There is no more Via headers; the response was intended for the proxy.
                if (responseOut.getHeader(ViaHeader.NAME) != null) sipProvider.sendResponse(responseOut);
            }
            LOG.trace(responseOut)
        },

        processTransactionTerminated: event => {
            if (event.isServerTransaction()) {
                const serverTransaction = event.getServerTransaction()

                if (!contextStorage.removeContext(serverTransaction)) {
                   LOG.debug("Ongoing Transaction")
                }
            }
        },

        processDialogTerminated: event => {
            LOG.debug('Dialog ' + event.getDialog() + ' has been terminated')
        },

        processTimeout: event => {
            LOG.debug('Transaction Time out')
        }
    }
}
<|endoftext|>