<commit_before>/**
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * 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';
var assert = require('assert');
var nock = require('nock');
var RequestHandler = require('../src/google-apis/auth-client.js');
var ErrorMessage = require('../src/classes/error-message.js');
var Configuration = require('../test/fixtures/configuration.js');
var createLogger = require('../src/logger.js');
var is = require('is');
var isObject = is.object;
var isString = is.string;
var isEmpty = is.empty;
var forEach = require('lodash.foreach');
var assign = require('lodash.assign');
var pick = require('lodash.pick');
var omitBy = require('lodash.omitby');
const ERR_TOKEN = '_@google_STACKDRIVER_INTEGRATION_TEST_ERROR__';
const env = (function(injectedEnv) {
  const envKeys = ['GOOGLE_APPLICATION_CREDENTIALS', 'GCLOUD_PROJECT',
    'NODE_ENV'];
  class InstancedEnv {
    constructor() {
      assign(this, injectedEnv);
      this._originalEnv = this._captureProcessProperties();
    }
    _captureProcessProperties() {
      return omitBy(pick(process.env, envKeys), value => !isString(value));
    }
    sterilizeProcess() {
      forEach(envKeys, (v, k) => delete process.env[k]);
      return this;
    }
    setProjectId() {
      assign(process.env, {
        GCLOUD_PROJECT: injectedEnv.projectId
      });
      return this;
    }
    setProjectNumber() {
      assign(process.env, {
        GCLOUD_PROJECT: injectedEnv.projectNumber
      });
      return this;
    }
    setKeyFilename() {
      assign(process.env, {
        GOOGLE_APPLICATION_CREDENTIALS: injectedEnv.keyFilename
      });
      return this;
    }
    setProduction() {
      assign(process.env, {
        NODE_ENV: 'production'
      });
      return this;
    }
    restoreProcessToOriginalState() {
      assign(process.env, this._originalEnv);
      return this;
    }
    injected() {
      return assign({}, injectedEnv);
    }
  }
  return new InstancedEnv();
}(require('../../../system-test/env.js')));
const SHOULD_RUN = (function() {
  if (!isString(env.injected().projectId)) {
    return new Error('The project id (projectId) was not set in the env');
  }
  if (!isString(env.injected().apiKey)) {
    return new Error('The api key (apiKey) was not set as an env variable');
  }
  if (!isString(env.injected().projectNumber)) {
    return new Error(
      'The project number (projectNumber) was not set in the env');
  }
  if (!isString(env.injected().keyFilename)) {
    return new Error(
      'The key filename (keyFilename) was not set in the env');
  }
  return true;
}());
const TEST_RUNNER = (function() {
  if (SHOULD_RUN instanceof Error) {
    console.log('Skipping error-reporting system tests:');
    console.log('  ' + SHOULD_RUN.message);
    return describe.skip;
  }
  return describe;
}());

(TEST_RUNNER)('Errors system tests', function() {
  describe('Request/Response lifecycle mocking', function() {
    var sampleError = new Error(ERR_TOKEN);
    var errorMessage = new ErrorMessage().setMessage(sampleError);
    var fakeService, client, logger;
    before(() => env.sterilizeProcess());
    beforeEach(function() {
      env.setProjectId().setKeyFilename().setProduction();
      fakeService = nock(
        'https://clouderrorreporting.googleapis.com./v1beta1/projects/' +
        process.env.GCLOUD_PROJECT
     ).persist().post('/events:report');
      logger = createLogger({logLevel: 5});
      client = new RequestHandler(
        new Configuration({ignoreEnvironmentCheck: true}, logger), logger);
    });
    afterEach(function() {
      env.sterilizeProcess();
      nock.cleanAll();
    });
    after(() => env.restoreProcessToOriginalState());
    describe('Receiving non-retryable errors', function() {
      it('Should fail', function(done) {
        this.timeout(5000);
        client.sendError({}, function(err, response,
          /* jshint unused:false */ body) {
          assert(err instanceof Error);
          assert.strictEqual(err.message.toLowerCase(),
            'message cannot be empty.');
          assert(isObject(response));
          assert.strictEqual(response.statusCode, 400);
          done();
        });
      });
    });
    describe('Receiving retryable errors', function() {
      it('Should retry', function(done) {
        this.timeout(25000);
        var tries = 0;
        var intendedTries = 4;
        fakeService.reply(429, function() {
          tries += 1;
          console.log('Mock Server Received Request:', tries + '/' +
            intendedTries);
          return {error: 'Please try again later'};
        });
        client.sendError(errorMessage, function(err, response,
          /* jshint unused:false */ body) {
          assert.strictEqual(tries, intendedTries);
          done();
        });
      });
    });
    describe('Using an API key', function() {
      it('Should provide the key as a query string on outgoing requests',
        function(done) {
          env.sterilizeProcess().setProjectId().setProduction();
          var key = env.apiKey;
          var client = new RequestHandler(new Configuration(
            {key: key, ignoreEnvironmentCheck: true},
            createLogger({logLevel: 5})));
          fakeService.query({key: key}).reply(200, function(uri) {
            assert(uri.indexOf('key=' + key) > -1);
            return {};
          });
          client.sendError(errorMessage, function() {
            done();
          });
        }
      );
    });
    describe('Callback-less invocation', function() {
      it('Should still execute the request', function(done) {
        fakeService.reply(200, function() {
          done();
        });
        client.sendError(errorMessage);
      });
    });
  });
  describe('System-live integration testing', function() {
    var sampleError = new Error(ERR_TOKEN);
    var errorMessage = new ErrorMessage().setMessage(sampleError.stack);
    describe('Client creation', function() {
      describe('Using only project id', function() {
        describe('As a runtime argument', function() {
          var cfg, logger;
          before(function() {
            env.sterilizeProcess().setKeyFilename();
            logger = createLogger({logLevel: 5});
            cfg = new Configuration({projectId: env.injected().projectId,
              ignoreEnvironmentCheck: true}, logger);
          });
          after(() => env.sterilizeProcess());
          it('Should not throw on initialization', function(done) {
            this.timeout(10000);
            assert.doesNotThrow(function() {
              (new RequestHandler(cfg, logger)).sendError(errorMessage,
                function(err, response, body) {
                  assert.strictEqual(err, null);
                  assert.strictEqual(response.statusCode, 200);
                  assert(isObject(body) && isEmpty(body));
                  done();
                }
             );
            });
          });
        });
        describe('As an env variable', function() {
          var cfg, logger;
          before(function() {
            env.sterilizeProcess().setProjectId().setKeyFilename();
            logger = createLogger({logLevel: 5});
            cfg = new Configuration({ignoreEnvironmentCheck: true}, logger);
          });
          after(() => env.sterilizeProcess());
          it('Should not throw on initialization', function(done) {
            this.timeout(10000);
            assert.doesNotThrow(function() {
              (new RequestHandler(cfg, logger)).sendError(errorMessage,
                function(err, response, body) {
                  assert.strictEqual(err, null);
                  assert.strictEqual(response.statusCode, 200);
                  assert(isObject(body) && isEmpty(body));
                  done();
                }
             );
            });
          });
        });
      });
      describe('Using only project number', function() {
        describe('As a runtime argument', function() {
          var cfg, logger;
          before(function() {
            env.sterilizeProcess().setKeyFilename();
            logger = createLogger({logLevel: 5});
            cfg = new Configuration({
              projectId: parseInt(env.injected().projectNumber),
              ignoreEnvironmentCheck: true
            }, logger);
          });
          after(() => env.sterilizeProcess());
          it('Should not throw on initialization', function(done) {
            this.timeout(10000);
            assert.doesNotThrow(function() {
              (new RequestHandler(cfg, logger)).sendError(errorMessage,
                function(err, response, body) {
                  assert.strictEqual(err, null);
                  assert.strictEqual(response.statusCode, 200);
                  assert(isObject(body) && isEmpty(body));
                  done();
                }
             );
            });
          });
        });
        describe('As an env variable', function() {
          var cfg, logger;
          before(function() {
            env.sterilizeProcess().setKeyFilename().setProjectNumber();
            logger = createLogger({logLevel: 5});
            cfg = new Configuration({ignoreEnvironmentCheck: true}, logger);
          });
          after(() => env.sterilizeProcess());
          it('Should not throw on initialization', function(done) {
            this.timeout(10000);
            assert.doesNotThrow(function() {
              (new RequestHandler(cfg, logger)).sendError(errorMessage,
                function(err, response, body) {
                  assert.strictEqual(err, null);
                  assert.strictEqual(response.statusCode, 200);
                  assert(isObject(body) && isEmpty(body));
                  done();
                }
             );
            });
          });
        });
      });
    });
    describe('Error behvaiour', function() {
      describe('With a configuration to not report errors', function() {
        var ERROR_STRING = [
          'Stackdriver error reporting client has not been configured to send',
          'errors, please check the NODE_ENV environment variable and make',
          'sure it is set to "production" or set the ignoreEnvironmentCheck',
          'property to true in the runtime configuration object'
        ].join(' ');
        var logger, client;
        before(function() {
          env.sterilizeProcess().setKeyFilename().setProjectId();
          process.env.NODE_ENV = 'null';
          logger = createLogger({logLevel: 5});
          client = new RequestHandler(new Configuration(undefined, logger),
            logger);
        });
        after(() => env.sterilizeProcess());
        it('Should callback with an error', function(done) {
          client.sendError({}, function(err, response,
            /* jshint unused:false */ body) {
            assert(err instanceof Error);
            assert.strictEqual(err.message, ERROR_STRING);
            assert.strictEqual(response, null);
            done();
          });
        });
      });
    });
    describe('Success behaviour', function() {
      var er = new Error(ERR_TOKEN);
      var em = new ErrorMessage().setMessage(er.stack);
      describe('Given a valid project id', function() {
        var logger, client, cfg;
        before(function() {
          env.sterilizeProcess();
          logger = createLogger({logLevel: 5});
          cfg = new Configuration({
            projectId: env.injected().projectId,
            ignoreEnvironmentCheck: true
          }, logger);
          client = new RequestHandler(cfg, logger);
        });
        after(() => env.sterilizeProcess());
        it('Should succeed in its request', function(done) {
          client.sendError(em, function(err, response, body) {
            assert.strictEqual(err, null);
            assert(isObject(body));
            assert(isEmpty(body));
            assert.strictEqual(response.statusCode, 200);
            done();
          });
        });
      });
      describe('Given a valid project number', function() {
        var logger, client, cfg;
        before(function() {
          env.sterilizeProcess();
          logger = createLogger({logLevel: 5});
          cfg = new Configuration({
            projectId: parseInt(env.injected().projectNumber),
            ignoreEnvironmentCheck: true
          }, logger);
          client = new RequestHandler(cfg, logger);
        });
        after(() => env.sterilizeProcess());
        it('Should succeed in its request', function(done) {
          client.sendError(em, function(err, response, body) {
            assert.strictEqual(err, null);
            assert(isObject(body));
            assert(isEmpty(body));
            assert.strictEqual(response.statusCode, 200);
            done();
          });
        });
      });
    });
  });
});
<commit_msg>error-reporting: System tests no longer use nested describe blocks (#2260)<commit_after>/**
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * 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';

var assert = require('assert');
var nock = require('nock');
var RequestHandler = require('../src/google-apis/auth-client.js');
var ErrorMessage = require('../src/classes/error-message.js');
var Configuration = require('../test/fixtures/configuration.js');
var createLogger = require('../src/logger.js');
var is = require('is');
var isObject = is.object;
var isString = is.string;
var isEmpty = is.empty;
var forEach = require('lodash.foreach');
var assign = require('lodash.assign');
var pick = require('lodash.pick');
var omitBy = require('lodash.omitby');

const ERR_TOKEN = '_@google_STACKDRIVER_INTEGRATION_TEST_ERROR__';

const envKeys = ['GOOGLE_APPLICATION_CREDENTIALS', 'GCLOUD_PROJECT',
    'NODE_ENV'];

class InstancedEnv {
  constructor(injectedEnv) {
    assign(this, injectedEnv);
    this.injectedEnv = injectedEnv;
    this._originalEnv = this._captureProcessProperties();
  }

  _captureProcessProperties() {
    return omitBy(pick(process.env, envKeys), value => !isString(value));
  }

  sterilizeProcess() {
    forEach(envKeys, (v, k) => delete process.env[k]);
    return this;
  }

  setProjectId() {
    assign(process.env, {
      GCLOUD_PROJECT: this.injectedEnv.projectId
    });
    return this;
  }

  setProjectNumber() {
    assign(process.env, {
      GCLOUD_PROJECT: this.injectedEnv.projectNumber
    });
    return this;
  }

  setKeyFilename() {
    assign(process.env, {
      GOOGLE_APPLICATION_CREDENTIALS: this.injectedEnv.keyFilename
    });
    return this;
  }

  setProduction() {
    assign(process.env, {
      NODE_ENV: 'production'
    });
    return this;
  }

  restoreProcessToOriginalState() {
    assign(process.env, this._originalEnv);
    return this;
  }

  injected() {
    return assign({}, this.injectedEnv);
  }
}

const env = new InstancedEnv(require('../../../system-test/env.js'));

function shouldRun() {
  var shouldRun = true;
  if (!isString(env.injected().projectId)) {
    console.log('The project id (projectId) was not set in the env');
    shouldRun = false;
  }

  if (!isString(env.injected().apiKey)) {
    console.log('The api key (apiKey) was not set as an env variable');
    shouldRun = false;
  }

  if (!isString(env.injected().projectNumber)) {
    console.log('The project number (projectNumber) was not set in the env');
    shouldRun = false;
  }

  if (!isString(env.injected().keyFilename)) {
    console.log('The key filename (keyFilename) was not set in the env');
    shouldRun = false;
  }

  return shouldRun;
}

if (!shouldRun()) {
  console.log('Skipping error-reporting system tests');
  process.exit(1);
}

describe('Request/Response lifecycle mocking', function() {
  var sampleError = new Error(ERR_TOKEN);
  var errorMessage = new ErrorMessage().setMessage(sampleError);
  var fakeService, client, logger;
  before(function() {
    env.sterilizeProcess();
  });

  beforeEach(function() {
    env.setProjectId().setKeyFilename().setProduction();
    fakeService = nock(
      'https://clouderrorreporting.googleapis.com./v1beta1/projects/' +
      process.env.GCLOUD_PROJECT
    ).persist().post('/events:report');
    logger = createLogger({logLevel: 5});
    client = new RequestHandler(
      new Configuration({ignoreEnvironmentCheck: true}, logger), logger);
  });

  afterEach(function() {
    env.sterilizeProcess();
    nock.cleanAll();
  });

  after(function() {
    env.restoreProcessToOriginalState();
  });

  it('Should fail when receiving non-retryable errors', function(done) {
    this.timeout(5000);
    client.sendError({}, function(err, response,
      /* jshint unused:false */ body) {
      assert(err instanceof Error);
      assert.strictEqual(err.message.toLowerCase(),
        'message cannot be empty.');
      assert(isObject(response));
      assert.strictEqual(response.statusCode, 400);
      done();
    });
  });

  it('Should retry when receiving retryable errors', function(done) {
    this.timeout(25000);
    var tries = 0;
    var intendedTries = 4;
    fakeService.reply(429, function() {
      tries += 1;
      console.log('Mock Server Received Request:', tries + '/' +
        intendedTries);
      return {error: 'Please try again later'};
    });
    client.sendError(errorMessage, function(err, response,
      /* jshint unused:false */ body) {
      assert.strictEqual(tries, intendedTries);
      done();
    });
  });

  it('Should provide the key as a query string on outgoing requests when ' +
    'using an API key', function(done) {
      env.sterilizeProcess().setProjectId().setProduction();
      var key = env.apiKey;
      var client = new RequestHandler(new Configuration(
        {key: key, ignoreEnvironmentCheck: true},
        createLogger({logLevel: 5})));
      fakeService.query({key: key}).reply(200, function(uri) {
        assert(uri.indexOf('key=' + key) > -1);
        return {};
      });
      client.sendError(errorMessage, function() {
        done();
      });
    }
  );

  it('Should still execute the request with a callback-less invocation',
    function(done) {
    fakeService.reply(200, function() {
      done();
    });
    client.sendError(errorMessage);
  });
});

describe('Client creation', function() {
  var sampleError = new Error(ERR_TOKEN);
  var errorMessage = new ErrorMessage().setMessage(sampleError.stack);
  after(function() {
    env.sterilizeProcess();
  });

  it('Should not throw on initialization when using only project id as a ' +
    'runtime argument', function(done) {
    env.sterilizeProcess().setKeyFilename();
    var logger = createLogger({logLevel: 5});
    var cfg = new Configuration({projectId: env.injected().projectId,
      ignoreEnvironmentCheck: true}, logger);
    this.timeout(10000);
    assert.doesNotThrow(function() {
      (new RequestHandler(cfg, logger)).sendError(errorMessage,
        function(err, response, body) {
          assert.strictEqual(err, null);
          assert.strictEqual(response.statusCode, 200);
          assert(isObject(body) && isEmpty(body));
          done();
        }
      );
    });
  });

  it('Should not throw on initialization when using only project id as an ' +
    'env variable', function(done) {
    env.sterilizeProcess().setProjectId().setKeyFilename();
    var logger = createLogger({logLevel: 5});
    var cfg = new Configuration({ignoreEnvironmentCheck: true}, logger);
    this.timeout(10000);
    assert.doesNotThrow(function() {
      (new RequestHandler(cfg, logger)).sendError(errorMessage,
        function(err, response, body) {
          assert.strictEqual(err, null);
          assert.strictEqual(response.statusCode, 200);
          assert(isObject(body) && isEmpty(body));
          done();
        }
      );
    });
  });

  it('Should not throw on initialization when using only project number as ' +
    'a runtime argument', function(done) {
    env.sterilizeProcess().setKeyFilename();
    var logger = createLogger({logLevel: 5});
    var cfg = new Configuration({
      projectId: parseInt(env.injected().projectNumber),
      ignoreEnvironmentCheck: true
    }, logger);
    this.timeout(10000);
    assert.doesNotThrow(function() {
      (new RequestHandler(cfg, logger)).sendError(errorMessage,
        function(err, response, body) {
          assert.strictEqual(err, null);
          assert.strictEqual(response.statusCode, 200);
          assert(isObject(body) && isEmpty(body));
          done();
        }
      );
    });
  });

  it('Should not throw on initialization when using only project number as ' +
    'an env variable', function(done) {
    env.sterilizeProcess().setKeyFilename().setProjectNumber();
    var logger = createLogger({logLevel: 5});
    var cfg = new Configuration({ignoreEnvironmentCheck: true}, logger);
    this.timeout(10000);
    assert.doesNotThrow(function() {
      (new RequestHandler(cfg, logger)).sendError(errorMessage,
        function(err, response, body) {
          assert.strictEqual(err, null);
          assert.strictEqual(response.statusCode, 200);
          assert(isObject(body) && isEmpty(body));
          done();
        }
      );
    });
  });
});

describe('Expected Behavior', function() {
  var ERROR_STRING = [
    'Stackdriver error reporting client has not been configured to send',
    'errors, please check the NODE_ENV environment variable and make',
    'sure it is set to "production" or set the ignoreEnvironmentCheck',
    'property to true in the runtime configuration object'
  ].join(' ');

  var er = new Error(ERR_TOKEN);
  var em = new ErrorMessage().setMessage(er.stack);

  after(function() {
    env.sterilizeProcess();
  });

  it('Should callback with an error with a configuration to not report errors',
    function(done) {
    env.sterilizeProcess().setKeyFilename().setProjectId();
    process.env.NODE_ENV = 'null';
    var logger = createLogger({logLevel: 5});
    var client = new RequestHandler(new Configuration(undefined, logger),
      logger);
    client.sendError({}, function(err, response,
      /* jshint unused:false */ body) {
      assert(err instanceof Error);
      assert.strictEqual(err.message, ERROR_STRING);
      assert.strictEqual(response, null);
      done();
    });
  });

  it('Should succeed in its request given a valid project id', function(done) {
    env.sterilizeProcess();
    var logger = createLogger({logLevel: 5});
    var cfg = new Configuration({
      projectId: env.injected().projectId,
      ignoreEnvironmentCheck: true
    }, logger);
    var client = new RequestHandler(cfg, logger);

    client.sendError(em, function(err, response, body) {
      assert.strictEqual(err, null);
      assert(isObject(body));
      assert(isEmpty(body));
      assert.strictEqual(response.statusCode, 200);
      done();
    });
  });

  it('Should succeed in its request given a valid project number',
    function(done) {
    env.sterilizeProcess();
    var logger = createLogger({logLevel: 5});
    var cfg = new Configuration({
      projectId: parseInt(env.injected().projectNumber),
      ignoreEnvironmentCheck: true
    }, logger);
    var client = new RequestHandler(cfg, logger);
    client.sendError(em, function(err, response, body) {
      assert.strictEqual(err, null);
      assert(isObject(body));
      assert(isEmpty(body));
      assert.strictEqual(response.statusCode, 200);
      done();
    });
  });
});
<|endoftext|>


<commit_before>/*
 * Author: Alexandre Havrileck (Oxyno-zeta)
 * Date: 09/07/16
 * Licence: See Readme
 */

/* ************************************* */
/* ********       REQUIRE       ******** */
/* ************************************* */
const logger = require('../../shared/logger')('[Core API]');

/* ************************************* */
/* ********       EXPORTS       ******** */
/* ************************************* */
module.exports = {
	sendArrayResponse: sendArrayResponse,
	sendResponse: sendResponse,
	sendTextResponse: sendTextResponse,
	getDefaultResponseBody: getDefaultResponseBody
};

/* ************************************* */
/* ********  PRIVATE FUNCTIONS  ******** */
/* ************************************* */

/* ************************************* */
/* ********   PUBLIC FUNCTIONS  ******** */
/* ************************************* */

/**
 * Send array response.
 * @param response {Object} Response
 * @param array {Array} Array
 * @param statusObject {Object} Status object (from APICodes)
 */
function sendArrayResponse(response, array, statusObject) {
	// Update response
	response.status(statusObject.code);
	// Debug part
	logger.debug(`Answer: Code = ${statusObject.code}, Text = ${array}`);
	// Send response
	response.send(array);
}

/**
 * Send text response.
 * @param response {Object} Response
 * @param text {String} Text
 * @param statusObject {Object} Status object (from APICodes)
 */
function sendTextResponse(response, text, statusObject) {
	// Update response
	response.status(statusObject.code);

	// Check status code
	if (statusObject.code === 204) {
		// Debug part
		logger.debug(`Answer: Code = ${statusObject.code}`);
		// Send response
		response.end();
	}
	else {
		// Debug part
		logger.debug(`Answer: Code = ${statusObject.code}, Text = ${text}`);
		// Send response
		response.send(text);
	}
}

/**
 * Send response.
 * @param response {Object} Response
 * @param body {Object} Body response
 * @param statusObject {Object} Status object (from APICodes)
 */
function sendResponse(response, body, statusObject) {
	// Update response
	response.status(statusObject.code);

	// Check status code
	if (statusObject.code === 204) {
		// Debug part
		logger.debug(`Answer: Code = ${statusObject.code}`);
		// Send response
		response.end();
	}
	else {
		// Update body
		body.reason = statusObject.reason;
		// Debug part
		logger.debug(`Answer: Code = ${statusObject.code}, Body = ${JSON.stringify(body)}`);
		// Send response
		response.json(body);
	}
}

/**
 * Return default response body.
 * @returns {{reason: string}}
 */
function getDefaultResponseBody() {
	return {
		reason: ''
	};
}
<commit_msg>[APIResponse] Update  body if necessary<commit_after>/*
 * Author: Alexandre Havrileck (Oxyno-zeta)
 * Date: 09/07/16
 * Licence: See Readme
 */

/* ************************************* */
/* ********       REQUIRE       ******** */
/* ************************************* */
const _ = require('lodash');
const logger = require('../../shared/logger')('[Core API]');

/* ************************************* */
/* ********       EXPORTS       ******** */
/* ************************************* */
module.exports = {
	sendArrayResponse: sendArrayResponse,
	sendResponse: sendResponse,
	sendTextResponse: sendTextResponse,
	getDefaultResponseBody: getDefaultResponseBody
};

/* ************************************* */
/* ********  PRIVATE FUNCTIONS  ******** */
/* ************************************* */

/* ************************************* */
/* ********   PUBLIC FUNCTIONS  ******** */
/* ************************************* */

/**
 * Send array response.
 * @param response {Object} Response
 * @param array {Array} Array
 * @param statusObject {Object} Status object (from APICodes)
 */
function sendArrayResponse(response, array, statusObject) {
	// Update response
	response.status(statusObject.code);
	// Debug part
	logger.debug(`Answer: Code = ${statusObject.code}, Text = ${array}`);
	// Send response
	response.send(array);
}

/**
 * Send text response.
 * @param response {Object} Response
 * @param text {String} Text
 * @param statusObject {Object} Status object (from APICodes)
 */
function sendTextResponse(response, text, statusObject) {
	// Update response
	response.status(statusObject.code);

	// Check status code
	if (statusObject.code === 204) {
		// Debug part
		logger.debug(`Answer: Code = ${statusObject.code}`);
		// Send response
		response.end();
	}
	else {
		// Debug part
		logger.debug(`Answer: Code = ${statusObject.code}, Text = ${text}`);
		// Send response
		response.send(text);
	}
}

/**
 * Send response.
 * @param response {Object} Response
 * @param body {Object} Body response
 * @param statusObject {Object} Status object (from APICodes)
 */
function sendResponse(response, body, statusObject) {
	// Update response
	response.status(statusObject.code);

	// Check status code
	if (statusObject.code === 204) {
		// Debug part
		logger.debug(`Answer: Code = ${statusObject.code}`);
		// Send response
		response.end();
	}
	else {
		// Update body if necessary
		if (_.has(body, 'reason')) {
			body.reason = statusObject.reason;
		}
		// Debug part
		logger.debug(`Answer: Code = ${statusObject.code}, Body = ${JSON.stringify(body)}`);
		// Send response
		response.json(body);
	}
}

/**
 * Return default response body.
 * @returns {{reason: string}}
 */
function getDefaultResponseBody() {
	return {
		reason: ''
	};
}
<|endoftext|>


<commit_before>import {promisify} from "bluebird";
import {exec} from "child_process";
import {stringifyEnvVars} from "lk-lambda-deploy";
import {v4} from "node-uuid";

import * as config from "config";
import {getDynamodb} from "services/aws";
import getGithubRef from "utils/get-github-ref";

const execAsync = promisify(exec);

export const path = "/deployments";
export const method = "post";
export const description = "Create a new deployment";
export const parameters = [{
    name: "deployment",
    in: "body",
    required: true,
    schema: {
        type: "object",
        properties: {
            awsAccessKeyId: {
                description: "AWS access key id",
                type: "string"
            },
            awsSecretAccessKey: {
                description: "AWS secret access key",
                type: "string"
            },
            awsRegion: {
                description: "AWS region",
                type: "string"
            },
            environmentName: {
                description: "Environment name",
                type: "string"
            },
            lambdaName: {
                description: "Lambda name",
                type: "string"
            }
        },
        additionalProperties: false,
        required: [
            "awsAccessKeyId",
            "awsSecretAccessKey",
            "awsRegion",
            "environmentName",
            "lambdaName"
        ]
    }
}];
export const responses = {
    "201": {
        description: "Deployment created"
    }
};
export async function handler (req, res) {
    const {
        awsRegion,
        awsAccessKeyId,
        awsSecretAccessKey,
        environmentName,
        lambdaName
    } = req.body;
    const dynamodb = getDynamodb({
        accessKeyId: awsAccessKeyId,
        secretAccessKey: awsSecretAccessKey,
        region: awsRegion
    });
    const {Item: lambda} = await dynamodb.getAsync({
        TableName : config.DYNAMODB_LAMBDAS_TABLE,
        Key: {
            name: lambdaName,
            environmentName: environmentName
        }
    });
    const githubRef = await getGithubRef(lambda);
    const environmentVariables = stringifyEnvVars(lambda.environmentVariables);
    await execAsync([
        "$(npm bin)/lk-lambda-deploy",
        `--awsAccessKeyId ${awsAccessKeyId}`,
        `--awsSecretAccessKey ${awsSecretAccessKey}`,
        `--awsRegion ${awsRegion}`,
        `--githubRef ${githubRef}`,
        `--lambdaName lk-${lambdaName}-${environmentName}`,
        `--lambdaRole ${lambda.role}`,
        `--environmentVariables "${environmentVariables}"`
    ].join(" "), {stdio: "inherit"});
    const deploymentId = v4();
    await dynamodb.putAsync({
        TableName : config.DYNAMODB_DEPLOYMENTS_TABLE,
        Item: {
            id: deploymentId,
            lambdaName: lambdaName,
            environmentName: environmentName,
            awsRegion: awsRegion,
            githubRef: githubRef,
            lambdaRole: lambda.role,
            environmentVariables: lambda.environmentVariables,
            timestamp: new Date().toISOString()
        }
    });
    res.status(201).send({id: deploymentId});
}
<commit_msg>Added error log<commit_after>import {promisify} from "bluebird";
import {exec} from "child_process";
import {stringifyEnvVars} from "lk-lambda-deploy";
import {v4} from "node-uuid";

import * as config from "config";
import {getDynamodb} from "services/aws";
import log from "services/logger";
import getGithubRef from "utils/get-github-ref";

const execAsync = promisify(exec);

export const path = "/deployments";
export const method = "post";
export const description = "Create a new deployment";
export const parameters = [{
    name: "deployment",
    in: "body",
    required: true,
    schema: {
        type: "object",
        properties: {
            awsAccessKeyId: {
                description: "AWS access key id",
                type: "string"
            },
            awsSecretAccessKey: {
                description: "AWS secret access key",
                type: "string"
            },
            awsRegion: {
                description: "AWS region",
                type: "string"
            },
            environmentName: {
                description: "Environment name",
                type: "string"
            },
            lambdaName: {
                description: "Lambda name",
                type: "string"
            }
        },
        additionalProperties: false,
        required: [
            "awsAccessKeyId",
            "awsSecretAccessKey",
            "awsRegion",
            "environmentName",
            "lambdaName"
        ]
    }
}];
export const responses = {
    "201": {
        description: "Deployment created"
    }
};
export async function handler (req, res) {
    try {
        const {
            awsRegion,
            awsAccessKeyId,
            awsSecretAccessKey,
            environmentName,
            lambdaName
        } = req.body;
        const dynamodb = getDynamodb({
            accessKeyId: awsAccessKeyId,
            secretAccessKey: awsSecretAccessKey,
            region: awsRegion
        });
        const {Item: lambda} = await dynamodb.getAsync({
            TableName : config.DYNAMODB_LAMBDAS_TABLE,
            Key: {
                name: lambdaName,
                environmentName: environmentName
            }
        });
        const githubRef = await getGithubRef(lambda);
        const environmentVariables = stringifyEnvVars(lambda.environmentVariables);
        await execAsync([
            "$(npm bin)/lk-lambda-deploy",
            `--awsAccessKeyId ${awsAccessKeyId}`,
            `--awsSecretAccessKey ${awsSecretAccessKey}`,
            `--awsRegion ${awsRegion}`,
            `--githubRef ${githubRef}`,
            `--lambdaName lk-${lambdaName}-${environmentName}`,
            `--lambdaRole ${lambda.role}`,
            `--environmentVariables "${environmentVariables}"`
        ].join(" "), {stdio: "inherit"});
        const deploymentId = v4();
        await dynamodb.putAsync({
            TableName : config.DYNAMODB_DEPLOYMENTS_TABLE,
            Item: {
                id: deploymentId,
                lambdaName: lambdaName,
                environmentName: environmentName,
                awsRegion: awsRegion,
                githubRef: githubRef,
                lambdaRole: lambda.role,
                environmentVariables: lambda.environmentVariables,
                timestamp: new Date().toISOString()
            }
        });
        res.status(201).send({id: deploymentId});
    } catch (e) {
        log.info(e);
    }
}
<|endoftext|>


<commit_before>import JoinAllLink from './JoinAllLink.svelte';
import contains from '../../common/contains';
import getArrayByTagName from '../../common/getArrayByTagName';
import getElementById from '../../common/getElementById';
import { pCL } from '../../support/layout';

function mountApp() {
  return new JoinAllLink({ target: getElementById('notifications') });
}

export default function injectJoinAllLink() {
  const newAttackGroup = getArrayByTagName('li', pCL)
    .find(contains('New attack group created.'));
  if (newAttackGroup) { mountApp(); }
}
<commit_msg>update join all position (#269)<commit_after>import JoinAllLink from './JoinAllLink.svelte';
import getElementById from '../../common/getElementById';

function mountApp(newGroup) {
  return new JoinAllLink({
    anchor: newGroup,
    target: newGroup.parentNode,
  });
}

export default function injectJoinAllLink() {
  const newGroup = getElementById('notification-guild-group');
  if (newGroup) { mountApp(newGroup); }
}
<|endoftext|>


<commit_before>describe('prx.modal', function () {
  beforeEach(module('prx.modal'));

  describe('state instrumentation', function () {
    var $scope, instrumentableState;

    beforeEach(inject(function ($rootScope) {
      $scope = $rootScope;
      instrumentableState = {
        views: {
          'modal@': {
            template: ''
          }
        }
      };
    }));

    it ('is triggered by $stateChangeStart', function () {
      $scope.$broadcast('$stateChangeStart', instrumentableState, {}, {});
      expect(instrumentableState.views['modal@'].instrumented).toBe(true);
    });

    it ('sets the modal data attribute to true', function () {
      $scope.$broadcast('$stateChangeStart', instrumentableState, {}, {});
      expect(instrumentableState.data.modal).toBe(true);
    });

    function getLink(data) {
      var div = angular.element('

');
      div.html(data);
      return div.children().eq(0);
    }

    it ('prefixes string templates with a link', function () {
      $scope.$broadcast('$stateChangeStart', instrumentableState, {}, {});
      var link = getLink(instrumentableState.views['modal@'].template);
      expect(link.attr('ui-sref')).toEqual('^');
      expect(link.hasClass('dismiss')).toBe(true);
    });

    it ('does nothing when there is nothing to be done', function () {
      instrumentableState.views['modal@'].template = undefined;
      $scope.$broadcast('$stateChangeStart', instrumentableState, {}, {});
    });

    it ('turns url templates into providers that prefix', inject(function ($injector, $templateCache) {
      var data, link;
      $templateCache.put('/foo.html', "
html
");
      instrumentableState.views['modal@'].template = undefined;
      instrumentableState.views['modal@'].templateUrl = '/foo.html';
      $scope.$broadcast('$stateChangeStart', instrumentableState, {}, {});
      expect(instrumentableState.views['modal@'].templateUrl).not.toBeDefined();
      $injector.invoke(instrumentableState.views['modal@'].templateProvider, null, {$stateParams: {}}).then(function (result) {
        data = result;
      });
      $scope.$digest();
      expect(data).toBeDefined();
      link = getLink(data);
      expect(link.attr('ui-sref')).toEqual('^');
      expect(link.hasClass('dismiss')).toBe(true);
    }));

    it ('works with function templateUrls', inject(function ($templateCache, $injector) {
      var data, link;
      $templateCache.put('/foo.html', "
html
");
      instrumentableState.views['modal@'].template = undefined;
      instrumentableState.views['modal@'].templateUrl = function (params) {
        return '/' + params.hokey + '.html';
      }
      $scope.$broadcast('$stateChangeStart', instrumentableState, {}, {});
      expect(instrumentableState.views['modal@'].templateUrl).not.toBeDefined();
      $injector.invoke(instrumentableState.views['modal@'].templateProvider, null, {$stateParams: {hokey: 'foo'}}).then(function (result) {
        data = result;
      });
      $scope.$digest();
      expect(data).toBeDefined();
      link = getLink(data);
      expect(link.attr('ui-sref')).toEqual('^');
      expect(link.hasClass('dismiss')).toBe(true);
    }));
  });

  describe ('visibility', function () {
    var modal, $scope;
    beforeEach(inject(function (prxModal, $rootScope) {
      modal = prxModal;
      $scope = $rootScope;
    }));

    it ('is shown on state change start when coming from an abstract state', function () {
      $scope.$broadcast('$stateChangeStart', {data:{modal:true}}, {}, {abstract:true});
      expect(modal.visible).toBe(true);
    });

    it ('is shown on success', function () {
      $scope.$broadcast('$stateChangeSuccess', {data:{modal:true}}, {}, {});
      expect(modal.visible).toBe(true);
    });

    it ('is hidden when state is not a modal state', function () {
      modal.visible = true;
      $scope.$broadcast('$stateChangeSuccess', {}, {}, {});
      expect(modal.visible).toBe(false);
    });
  });

  describe ('ModalCtrl', function () {
    var error, modal, controller;

    beforeEach(inject(function ($controller) {
      error = jasmine.createSpyObj('prxError', ['hasError']);
      modal = {visible: false};
      controller = $controller('ModalCtrl', {
        prxError: error,
        prxModal: modal
      });
    }));

    it ('is error when prxError has error', function () {
      error.hasError.and.returnValue(true);
      expect(controller.error()).toBe(true);
    });

    it ('is visible when prxError has error', function () {
      error.hasError.and.returnValue(true);
      expect(controller.visible()).toBe(true);
    });

    it ('is visible when prxError has no error but modal is visible', function () {
      modal.visible = true;
      expect(controller.visible()).toBe(true);
    });

    it ('is not visible otherwise', function () {
      expect(controller.visible()).toBe(false);
    });
  });

  describe ('directive', function () {
    it ('compiles', inject(function ($compile) {
      $compile('<prx-modal></prx-modal>')
    }));
  });
});
<commit_msg>semicolons<commit_after>describe('prx.modal', function () {
  beforeEach(module('prx.modal'));

  describe('state instrumentation', function () {
    var $scope, instrumentableState;

    beforeEach(inject(function ($rootScope) {
      $scope = $rootScope;
      instrumentableState = {
        views: {
          'modal@': {
            template: ''
          }
        }
      };
    }));

    it ('is triggered by $stateChangeStart', function () {
      $scope.$broadcast('$stateChangeStart', instrumentableState, {}, {});
      expect(instrumentableState.views['modal@'].instrumented).toBe(true);
    });

    it ('sets the modal data attribute to true', function () {
      $scope.$broadcast('$stateChangeStart', instrumentableState, {}, {});
      expect(instrumentableState.data.modal).toBe(true);
    });

    function getLink(data) {
      var div = angular.element('

');
      div.html(data);
      return div.children().eq(0);
    }

    it ('prefixes string templates with a link', function () {
      $scope.$broadcast('$stateChangeStart', instrumentableState, {}, {});
      var link = getLink(instrumentableState.views['modal@'].template);
      expect(link.attr('ui-sref')).toEqual('^');
      expect(link.hasClass('dismiss')).toBe(true);
    });

    it ('does nothing when there is nothing to be done', function () {
      instrumentableState.views['modal@'].template = undefined;
      $scope.$broadcast('$stateChangeStart', instrumentableState, {}, {});
    });

    it ('turns url templates into providers that prefix', inject(function ($injector, $templateCache) {
      var data, link;
      $templateCache.put('/foo.html', "
html
");
      instrumentableState.views['modal@'].template = undefined;
      instrumentableState.views['modal@'].templateUrl = '/foo.html';
      $scope.$broadcast('$stateChangeStart', instrumentableState, {}, {});
      expect(instrumentableState.views['modal@'].templateUrl).not.toBeDefined();
      $injector.invoke(instrumentableState.views['modal@'].templateProvider, null, {$stateParams: {}}).then(function (result) {
        data = result;
      });
      $scope.$digest();
      expect(data).toBeDefined();
      link = getLink(data);
      expect(link.attr('ui-sref')).toEqual('^');
      expect(link.hasClass('dismiss')).toBe(true);
    }));

    it ('works with function templateUrls', inject(function ($templateCache, $injector) {
      var data, link;
      $templateCache.put('/foo.html', "
html
");
      instrumentableState.views['modal@'].template = undefined;
      instrumentableState.views['modal@'].templateUrl = function (params) {
        return '/' + params.hokey + '.html';
      };
      $scope.$broadcast('$stateChangeStart', instrumentableState, {}, {});
      expect(instrumentableState.views['modal@'].templateUrl).not.toBeDefined();
      $injector.invoke(instrumentableState.views['modal@'].templateProvider, null, {$stateParams: {hokey: 'foo'}}).then(function (result) {
        data = result;
      });
      $scope.$digest();
      expect(data).toBeDefined();
      link = getLink(data);
      expect(link.attr('ui-sref')).toEqual('^');
      expect(link.hasClass('dismiss')).toBe(true);
    }));
  });

  describe ('visibility', function () {
    var modal, $scope;
    beforeEach(inject(function (prxModal, $rootScope) {
      modal = prxModal;
      $scope = $rootScope;
    }));

    it ('is shown on state change start when coming from an abstract state', function () {
      $scope.$broadcast('$stateChangeStart', {data:{modal:true}}, {}, {abstract:true});
      expect(modal.visible).toBe(true);
    });

    it ('is shown on success', function () {
      $scope.$broadcast('$stateChangeSuccess', {data:{modal:true}}, {}, {});
      expect(modal.visible).toBe(true);
    });

    it ('is hidden when state is not a modal state', function () {
      modal.visible = true;
      $scope.$broadcast('$stateChangeSuccess', {}, {}, {});
      expect(modal.visible).toBe(false);
    });
  });

  describe ('ModalCtrl', function () {
    var error, modal, controller;

    beforeEach(inject(function ($controller) {
      error = jasmine.createSpyObj('prxError', ['hasError']);
      modal = {visible: false};
      controller = $controller('ModalCtrl', {
        prxError: error,
        prxModal: modal
      });
    }));

    it ('is error when prxError has error', function () {
      error.hasError.and.returnValue(true);
      expect(controller.error()).toBe(true);
    });

    it ('is visible when prxError has error', function () {
      error.hasError.and.returnValue(true);
      expect(controller.visible()).toBe(true);
    });

    it ('is visible when prxError has no error but modal is visible', function () {
      modal.visible = true;
      expect(controller.visible()).toBe(true);
    });

    it ('is not visible otherwise', function () {
      expect(controller.visible()).toBe(false);
    });
  });

  describe ('directive', function () {
    it ('compiles', inject(function ($compile) {
      $compile('<prx-modal></prx-modal>');
    }));
  });
});
<|endoftext|>


<commit_before>import { slice, concat, without, reduce, remove, assign, find, forEach, union, filter, uniqBy } from 'lodash';

import {  ERROR, AUTH_CONNECTED, Socket, SearchMessage, DiscoverIndicesMessage, DiscoverFieldsMessage } from '../utils/index';

import {  INDICES_RECEIVE, INDICES_REQUEST } from '../modules/indices/index';
import {  FIELDS_RECEIVE, FIELDS_REQUEST } from '../modules/fields/index';
import {  NODES_DELETE, NODES_HIGHLIGHT, NODE_UPDATE, NODE_SELECT, NODES_SELECT, NODES_DESELECT, SELECTION_CLEAR } from '../modules/graph/index';
import {  SEARCH_DELETE, ITEMS_RECEIVE, ITEMS_REQUEST } from '../modules/search/index';
import {  TABLE_COLUMN_ADD, TABLE_COLUMN_REMOVE, INDEX_ADD, INDEX_DELETE, FIELD_ADD, FIELD_DELETE, DATE_FIELD_ADD, DATE_FIELD_DELETE, NORMALIZATION_ADD, NORMALIZATION_DELETE, INITIAL_STATE_RECEIVE } from '../modules/data/index';

import { normalize, fieldLocator } from '../helpers/index';
import getNodesAndLinks from "../helpers/getNodesAndLinks";
import removeNodesAndLinks from "../helpers/removeNodesAndLinks";
import getHighlightItem from "../helpers/getHighlightItem";

export const defaultState = {
    isFetching: false,
    itemsFetching: false,
    noMoreHits: false,
    didInvalidate: false,
    connected: false,
    total: 0,
    node: [],
    datasources: [],
    highlight_nodes: [],
    columns: [],
    fields: [],
    date_fields: [],
    normalizations: [],
    indexes: [],
    items: [],
    searches: [],
    nodes: [], // all nodes
    links: [], // relations between nodes
    errors: null
};

export default function entries(state = defaultState, action) {
    switch (action.type) {
        case SELECTION_CLEAR:
            return Object.assign({}, state, {
                node: [],
            });
        case INDEX_DELETE:
            const index = find(state.indexes, (i) => {
                return (i.id == action.index);
            });

            var indexes = without(state.indexes, index);
            return Object.assign({}, state, {
                indexes: indexes,
            });
        case NODES_DELETE:
            var items = concat(state.items);
            var node = concat(state.node);
            var nodes = concat(state.nodes);
            var links = concat(state.links);

            // remove from selection as well
            remove(node, (p) => {
                return find(action.nodes, (o) => {
                    return o.id == p.id;
                });
            });

            remove(nodes, (p) => {
                return find(action.nodes, (o) => {
                    return o.id == p.id;
                });
            });

            remove(links, (p) => {
                return find(action.nodes, (o) => {
                    return p.source == o.id || p.target == o.id;
                });
            });

            return Object.assign({}, state, {
                items: items,
                node: node,
                nodes: nodes,
                links: links
            });
        case SEARCH_DELETE:
            const searches = without(state.searches, action.search);
            var items = concat(state.items);

            items = items.filter(item => item.query !== action.search.q);

            // todo(nl5887): remove related nodes and links
            const result = removeNodesAndLinks(state.nodes, state.links, action.search.q);

            return Object.assign({}, state, {
                searches: searches,
                items: items,
                nodes: result.nodes,
                links: result.links,
                selectedNodes: [],
                highlight_nodes: [],
                node: []
            });
        case TABLE_COLUMN_ADD:
            return Object.assign({}, state, {
                columns: concat(state.columns, action.field),
            });
        case TABLE_COLUMN_REMOVE:
            return Object.assign({}, state, {
                columns: without(state.columns, action.field)
            });
        case FIELD_ADD:
            const existing = state.fields.find(field => field.path === action.path);

            if (existing) {
                // Field was already in store, don't add duplicates
                return state;
            }

            const firstChar = action.path.charAt(0).toUpperCase();
            const fieldsWithSameChar = state.fields.filter(field => field.icon.indexOf(firstChar) === 0);
            let icon;

            if (fieldsWithSameChar.length === 0) {
                icon = firstChar;
            } else {
                // Append a number to the icon if multiple fields share the same
                // first character
                icon = firstChar + (fieldsWithSameChar.length + 1);
            }

            let newField = {
                path: action.path,
                icon: icon
            };

            return Object.assign({}, state, {
                fields: concat(state.fields, newField)
            });
        case FIELD_DELETE:
            return Object.assign({}, state, {
                fields: without(state.fields, action.field)
            });
        case NORMALIZATION_ADD:
            let normalization = action.normalization;
            normalization.re = new RegExp(normalization.regex, "i");
            return Object.assign({}, state, {
                normalizations: concat(state.normalizations, normalization)
            });
        case NORMALIZATION_DELETE:
            return Object.assign({}, state, {
                normalizations: without(state.normalizations, action.normalization)
            });
        case DATE_FIELD_ADD:
            return Object.assign({}, state, {
                date_fields: concat(state.date_fields, action.field)
            });
        case DATE_FIELD_DELETE:
            return Object.assign({}, state, {
                date_fields: without(state.date_fields, action.field)
            });
        case NODES_HIGHLIGHT:
            const highlightItems = [];

            action.highlight_nodes.forEach(node => {
                const item = Object.assign({}, state.items.find(item => item.id === node.items[0]));
                const highlightItem = getHighlightItem(item, node, state.fields);

                highlightItems.push(highlightItem);
            });

            return Object.assign({}, state, {
                highlight_nodes: highlightItems
            });
        case NODES_SELECT:
            const newNodes = [];

            action.nodes.forEach(node => {
                // First check if it's already selected, don't add duplicates
                if (state.node.indexOf(node) === -1) {
                    newNodes.push(node);
                }
            });

            return Object.assign({}, state, {
                node: concat(state.node, newNodes)
            });
        case NODES_DESELECT:
            return Object.assign({}, state, {
                node: filter(state.node, (o) => {
                    return !find(action.nodes, o);
                })
            });
        case NODE_UPDATE:
            let nodes = concat(state.nodes, []);

            let n = find(nodes, {id: action.node_id });
            if (n) {
                n = assign(n, action.params);
            }

            return Object.assign({}, state, {
                nodes: nodes
            });
        case NODE_SELECT:
            return Object.assign({}, state, {
                node: concat(state.node, action.node)
            });
        case ERROR:
            console.debug(action);
            return Object.assign({}, state, {
                errors: action.errors
            });
        case AUTH_CONNECTED:
            return Object.assign({}, state, {
                isFetching: false,
                didInvalidate: false,
                ...action
            });
        case ITEMS_REQUEST: {
            // if we searched before, just retrieve extra results for query
            const search = find(state.searches, (o) => o.q == action.query) || { items: [] };

            let from = search.items.length || 0;

            let message = {
                datasources: action.datasources,
                query: action.query,
                from: from,
                size: action.size,
                // todo: remove this, but requires a backend change. now the server wont respond if we dont send a color
                color: '#de79f2'
            };
            Socket.ws.postMessage(message);

            return Object.assign({}, state, {
                isFetching: true,
                itemsFetching: true,
                didInvalidate: false
            });
        }
        case ITEMS_RECEIVE: {
            const searches = concat(state.searches, []);
            const items = action.items.results === null ? [] : action.items.results;

            // should we update existing search, or add new, do we still need items?
            let search = find(state.searches, (o) => o.q == action.items.query);
            if (search) {
                search.items = concat(search.items, action.items.results);
            } else {
                const colors = ['#de79f2', '#917ef2', '#6d83f2', '#499df2', '#49d6f2', '#00ccaa', '#fac04b', '#bf8757', '#ff884d', '#ff7373', '#ff5252', '#6b8fb3'];
                // Sequentially uses the available colors, and starts again from the start when we exceed the amount of colors
                const colorIndex = (state.searches.length % colors.length + colors.length) % colors.length;
                const color = colors[colorIndex];

                search = {
                    q: action.items.query,
                    color: color,
                    total: action.items.total,
                    items: items
                };

                searches.push(search);
            }

            // Save per item for which query we received it (so we can keep track of where data came from)
            items.forEach(item => {
                item.query = search.q;
            });

            // todo(nl5887): should we start a webworker here, the webworker can have its own permanent cache?

            // update nodes and links
            const {nodes, links} = getNodesAndLinks(
                state.nodes,
                state.links,
                items,
                state.fields,
                search,
                state.normalizations
            );

            return Object.assign({}, state, {
                errors: null,
                nodes: nodes,
                links: links,
                items: concat(state.items, items),
                searches: searches,
                isFetching: false,
                itemsFetching: false,
                didInvalidate: false
            });
        }
        case INDICES_REQUEST:
            Socket.ws.postMessage(
                {
                    host: [action.payload.server]
                },
                INDICES_REQUEST
            );

            return Object.assign({}, state, {
                isFetching: true,
                didInvalidate: false
            });

        case INDICES_RECEIVE:
            const indices = uniqBy(union(state.indexes, action.payload.indices.map((index) => {
                return {
                    id: `${action.payload.server}${index}`,
                    server: action.payload.server,
                    name: index
                };
            })), (i) => i.id);

            return Object.assign({}, state, {
                indexes: indices,
                isFetching: false
            });

        case FIELDS_REQUEST:
            return Object.assign({}, state, {
                isFetching: true
            });

        case FIELDS_RECEIVE:
            return Object.assign({}, state, {
                isFetching: false
            });

        case INITIAL_STATE_RECEIVE:
        return Object.assign({}, state, {
            datasources: action.initial_state.datasources,
        });

        default:
            return state;
    }
}
<commit_msg>change node colors<commit_after>import { slice, concat, without, reduce, remove, assign, find, forEach, union, filter, uniqBy } from 'lodash';

import {  ERROR, AUTH_CONNECTED, Socket, SearchMessage, DiscoverIndicesMessage, DiscoverFieldsMessage } from '../utils/index';

import {  INDICES_RECEIVE, INDICES_REQUEST } from '../modules/indices/index';
import {  FIELDS_RECEIVE, FIELDS_REQUEST } from '../modules/fields/index';
import {  NODES_DELETE, NODES_HIGHLIGHT, NODE_UPDATE, NODE_SELECT, NODES_SELECT, NODES_DESELECT, SELECTION_CLEAR } from '../modules/graph/index';
import {  SEARCH_DELETE, ITEMS_RECEIVE, ITEMS_REQUEST } from '../modules/search/index';
import {  TABLE_COLUMN_ADD, TABLE_COLUMN_REMOVE, INDEX_ADD, INDEX_DELETE, FIELD_ADD, FIELD_DELETE, DATE_FIELD_ADD, DATE_FIELD_DELETE, NORMALIZATION_ADD, NORMALIZATION_DELETE, INITIAL_STATE_RECEIVE } from '../modules/data/index';

import { normalize, fieldLocator } from '../helpers/index';
import getNodesAndLinks from "../helpers/getNodesAndLinks";
import removeNodesAndLinks from "../helpers/removeNodesAndLinks";
import getHighlightItem from "../helpers/getHighlightItem";

export const defaultState = {
    isFetching: false,
    itemsFetching: false,
    noMoreHits: false,
    didInvalidate: false,
    connected: false,
    total: 0,
    node: [],
    datasources: [],
    highlight_nodes: [],
    columns: [],
    fields: [],
    date_fields: [],
    normalizations: [],
    indexes: [],
    items: [],
    searches: [],
    nodes: [], // all nodes
    links: [], // relations between nodes
    errors: null
};

export default function entries(state = defaultState, action) {
    switch (action.type) {
        case SELECTION_CLEAR:
            return Object.assign({}, state, {
                node: [],
            });
        case INDEX_DELETE:
            const index = find(state.indexes, (i) => {
                return (i.id == action.index);
            });

            var indexes = without(state.indexes, index);
            return Object.assign({}, state, {
                indexes: indexes,
            });
        case NODES_DELETE:
            var items = concat(state.items);
            var node = concat(state.node);
            var nodes = concat(state.nodes);
            var links = concat(state.links);

            // remove from selection as well
            remove(node, (p) => {
                return find(action.nodes, (o) => {
                    return o.id == p.id;
                });
            });

            remove(nodes, (p) => {
                return find(action.nodes, (o) => {
                    return o.id == p.id;
                });
            });

            remove(links, (p) => {
                return find(action.nodes, (o) => {
                    return p.source == o.id || p.target == o.id;
                });
            });

            return Object.assign({}, state, {
                items: items,
                node: node,
                nodes: nodes,
                links: links
            });
        case SEARCH_DELETE:
            const searches = without(state.searches, action.search);
            var items = concat(state.items);

            items = items.filter(item => item.query !== action.search.q);

            // todo(nl5887): remove related nodes and links
            const result = removeNodesAndLinks(state.nodes, state.links, action.search.q);

            return Object.assign({}, state, {
                searches: searches,
                items: items,
                nodes: result.nodes,
                links: result.links,
                selectedNodes: [],
                highlight_nodes: [],
                node: []
            });
        case TABLE_COLUMN_ADD:
            return Object.assign({}, state, {
                columns: concat(state.columns, action.field),
            });
        case TABLE_COLUMN_REMOVE:
            return Object.assign({}, state, {
                columns: without(state.columns, action.field)
            });
        case FIELD_ADD:
            const existing = state.fields.find(field => field.path === action.path);

            if (existing) {
                // Field was already in store, don't add duplicates
                return state;
            }

            const firstChar = action.path.charAt(0).toUpperCase();
            const fieldsWithSameChar = state.fields.filter(field => field.icon.indexOf(firstChar) === 0);
            let icon;

            if (fieldsWithSameChar.length === 0) {
                icon = firstChar;
            } else {
                // Append a number to the icon if multiple fields share the same
                // first character
                icon = firstChar + (fieldsWithSameChar.length + 1);
            }

            let newField = {
                path: action.path,
                icon: icon
            };

            return Object.assign({}, state, {
                fields: concat(state.fields, newField)
            });
        case FIELD_DELETE:
            return Object.assign({}, state, {
                fields: without(state.fields, action.field)
            });
        case NORMALIZATION_ADD:
            let normalization = action.normalization;
            normalization.re = new RegExp(normalization.regex, "i");
            return Object.assign({}, state, {
                normalizations: concat(state.normalizations, normalization)
            });
        case NORMALIZATION_DELETE:
            return Object.assign({}, state, {
                normalizations: without(state.normalizations, action.normalization)
            });
        case DATE_FIELD_ADD:
            return Object.assign({}, state, {
                date_fields: concat(state.date_fields, action.field)
            });
        case DATE_FIELD_DELETE:
            return Object.assign({}, state, {
                date_fields: without(state.date_fields, action.field)
            });
        case NODES_HIGHLIGHT:
            const highlightItems = [];

            action.highlight_nodes.forEach(node => {
                const item = Object.assign({}, state.items.find(item => item.id === node.items[0]));
                const highlightItem = getHighlightItem(item, node, state.fields);

                highlightItems.push(highlightItem);
            });

            return Object.assign({}, state, {
                highlight_nodes: highlightItems
            });
        case NODES_SELECT:
            const newNodes = [];

            action.nodes.forEach(node => {
                // First check if it's already selected, don't add duplicates
                if (state.node.indexOf(node) === -1) {
                    newNodes.push(node);
                }
            });

            return Object.assign({}, state, {
                node: concat(state.node, newNodes)
            });
        case NODES_DESELECT:
            return Object.assign({}, state, {
                node: filter(state.node, (o) => {
                    return !find(action.nodes, o);
                })
            });
        case NODE_UPDATE:
            let nodes = concat(state.nodes, []);

            let n = find(nodes, {id: action.node_id });
            if (n) {
                n = assign(n, action.params);
            }

            return Object.assign({}, state, {
                nodes: nodes
            });
        case NODE_SELECT:
            return Object.assign({}, state, {
                node: concat(state.node, action.node)
            });
        case ERROR:
            console.debug(action);
            return Object.assign({}, state, {
                errors: action.errors
            });
        case AUTH_CONNECTED:
            return Object.assign({}, state, {
                isFetching: false,
                didInvalidate: false,
                ...action
            });
        case ITEMS_REQUEST: {
            // if we searched before, just retrieve extra results for query
            const search = find(state.searches, (o) => o.q == action.query) || { items: [] };

            let from = search.items.length || 0;

            let message = {
                datasources: action.datasources,
                query: action.query,
                from: from,
                size: action.size,
                // todo: remove this, but requires a backend change. now the server wont respond if we dont send a color
                color: '#de79f2'
            };
            Socket.ws.postMessage(message);

            return Object.assign({}, state, {
                isFetching: true,
                itemsFetching: true,
                didInvalidate: false
            });
        }
        case ITEMS_RECEIVE: {
            const searches = concat(state.searches, []);
            const items = action.items.results === null ? [] : action.items.results;

            // should we update existing search, or add new, do we still need items?
            let search = find(state.searches, (o) => o.q == action.items.query);
            if (search) {
                search.items = concat(search.items, action.items.results);
            } else {
                const colors = [
                    '#de79f2',
                    '#917ef2',
                    '#499df2',
                    '#49d6f2',
                    '#00ccaa',
                    '#fac04b',
                    '#bf8757',
                    '#ff884d',
                    '#ff7373',
                    '#ff5252',
                    '#6b8fb3'
                ];
                // Sequentially uses the available colors, and starts again from the start when we exceed the amount of colors
                const colorIndex = (state.searches.length % colors.length + colors.length) % colors.length;
                const color = colors[colorIndex];

                search = {
                    q: action.items.query,
                    color: color,
                    total: action.items.total,
                    items: items
                };

                searches.push(search);
            }

            // Save per item for which query we received it (so we can keep track of where data came from)
            items.forEach(item => {
                item.query = search.q;
            });

            // todo(nl5887): should we start a webworker here, the webworker can have its own permanent cache?

            // update nodes and links
            const {nodes, links} = getNodesAndLinks(
                state.nodes,
                state.links,
                items,
                state.fields,
                search,
                state.normalizations
            );

            return Object.assign({}, state, {
                errors: null,
                nodes: nodes,
                links: links,
                items: concat(state.items, items),
                searches: searches,
                isFetching: false,
                itemsFetching: false,
                didInvalidate: false
            });
        }
        case INDICES_REQUEST:
            Socket.ws.postMessage(
                {
                    host: [action.payload.server]
                },
                INDICES_REQUEST
            );

            return Object.assign({}, state, {
                isFetching: true,
                didInvalidate: false
            });

        case INDICES_RECEIVE:
            const indices = uniqBy(union(state.indexes, action.payload.indices.map((index) => {
                return {
                    id: `${action.payload.server}${index}`,
                    server: action.payload.server,
                    name: index
                };
            })), (i) => i.id);

            return Object.assign({}, state, {
                indexes: indices,
                isFetching: false
            });

        case FIELDS_REQUEST:
            return Object.assign({}, state, {
                isFetching: true
            });

        case FIELDS_RECEIVE:
            return Object.assign({}, state, {
                isFetching: false
            });

        case INITIAL_STATE_RECEIVE:
        return Object.assign({}, state, {
            datasources: action.initial_state.datasources,
        });

        default:
            return state;
    }
}
<|endoftext|>


<commit_before>import {
  MONITOR_ACTION, SELECT_MONITOR, SELECT_MONITOR_TAB, UPDATE_MONITOR_STATE,
  TOGGLE_SLIDER, TOGGLE_DISPATCHER
} from '../constants/actionTypes';

const initialState = {
  selected: 'InspectorMonitor',
  monitorState: undefined,
  sliderIsOpen: true,
  dispatcherIsOpen: false
};

export function dispatchMonitorAction(state, action) {
  return {
    ...state,
    monitorState: action.action.newMonitorState ||
      action.monitorReducer(action.monitorProps, state.monitorState, action.action)
  };
}

export default function monitor(state = initialState, action) {
  switch (action.type) {
    case MONITOR_ACTION:
      return dispatchMonitorAction(state, action);
    case SELECT_MONITOR:
      let monitorState = state.monitorState;
      if (action.monitorState) {
        monitorState = {
          ...action.monitorState,
          __overwritten__: action.monitor
        };
      }
      return {
        ...state,
        monitorState,
        selected: action.monitor
      };
    case UPDATE_MONITOR_STATE:
      return {
        ...state,
        monitorState: {
          ...state.monitorState,
          ...action.nextState
        }
      };
    case TOGGLE_SLIDER:
      return {
        ...state,
        sliderIsOpen: !state.sliderIsOpen
      };
    case TOGGLE_DISPATCHER:
      return {
        ...state,
        dispatcherIsOpen: !state.dispatcherIsOpen
      };
    default:
      return state;
  }
}
<commit_msg>Traverse inside Chart graph<commit_after>import {
  MONITOR_ACTION, SELECT_MONITOR, SELECT_MONITOR_TAB, UPDATE_MONITOR_STATE,
  TOGGLE_SLIDER, TOGGLE_DISPATCHER
} from '../constants/actionTypes';

const initialState = {
  selected: 'InspectorMonitor',
  monitorState: undefined,
  sliderIsOpen: true,
  dispatcherIsOpen: false
};

export function dispatchMonitorAction(state, action) {
  return {
    ...state,
    monitorState: action.action.newMonitorState ||
      action.monitorReducer(action.monitorProps, state.monitorState, action.action)
  };
}

export default function monitor(state = initialState, action) {
  switch (action.type) {
    case MONITOR_ACTION:
      return dispatchMonitorAction(state, action);
    case SELECT_MONITOR:
      let monitorState = state.monitorState;
      if (action.monitorState) {
        monitorState = {
          ...action.monitorState,
          __overwritten__: action.monitor
        };
      }
      return {
        ...state,
        monitorState,
        selected: action.monitor
      };
    case UPDATE_MONITOR_STATE:
      let inspectedStatePath = state.monitorState.inspectedStatePath;
      if (action.nextState.inspectedStatePath) {
        inspectedStatePath = [
          ...inspectedStatePath.slice(0, -1),
          ...action.nextState.inspectedStatePath
        ];
      }
      return {
        ...state,
        monitorState: {
          ...state.monitorState,
          ...action.nextState,
          inspectedStatePath
        }
      };
    case TOGGLE_SLIDER:
      return {
        ...state,
        sliderIsOpen: !state.sliderIsOpen
      };
    case TOGGLE_DISPATCHER:
      return {
        ...state,
        dispatcherIsOpen: !state.dispatcherIsOpen
      };
    default:
      return state;
  }
}
<|endoftext|>


<commit_before>const {
  BrowserView,
  BrowserWindow,
  app,
  session,
  shell,
} = require('electron');
const path = require('path');
const fsExtra = require('fs-extra');

const appJson = require('../app.json');

const { getPreferences } = require('./preferences');
const {
  getWorkspace,
  setWorkspace,
} = require('./workspaces');

const sendToAllWindows = require('./send-to-all-windows');

const views = {};
const badgeCounts = {};
const didFailLoad = {};

const extractDomain = (fullUrl) => {
  const matches = fullUrl.match(/^https?:\/\/([^/?#]+)(?:[/?#]|$)/i);
  const domain = matches && matches[1];
  return domain ? domain.replace('www.', '') : null;
};

const addView = (browserWindow, workspace) => {
  const {
    rememberLastPageVisited,
    shareWorkspaceBrowsingData,
    unreadCountBadge,
  } = getPreferences();

  const view = new BrowserView({
    webPreferences: {
      nodeIntegration: false,
      contextIsolation: true,
      partition: shareWorkspaceBrowsingData ? 'persist:shared' : `persist:${workspace.id}`,
      preload: path.join(__dirname, '..', 'preload', 'view.js'),
    },
  });

  if (workspace.active) {
    browserWindow.setBrowserView(view);

    const contentSize = browserWindow.getContentSize();

    const offsetTitlebar = process.platform !== 'darwin' || global.showSidebar || global.attachToMenubar ? 0 : 22;
    const x = global.showSidebar ? 68 : 0;
    const y = global.showNavigationBar ? 36 + offsetTitlebar : 0 + offsetTitlebar;

    view.setBounds({
      x,
      y,
      width: contentSize[0] - x,
      height: contentSize[1] - y,
    });
    view.setAutoResize({
      width: true,
      height: true,
    });
  }

  view.webContents.on('did-start-loading', () => {
    if (getWorkspace(workspace.id).active) {
      didFailLoad[workspace.id] = false;
      sendToAllWindows('update-did-fail-load', false);
      sendToAllWindows('update-is-loading', true);
    }
  });

  view.webContents.on('did-stop-loading', () => {
    if (getWorkspace(workspace.id).active) {
      sendToAllWindows('update-is-loading', false);
    }

    const currentUrl = view.webContents.getURL();
    setWorkspace(workspace.id, {
      lastUrl: currentUrl,
    });
  });

  // https://electronjs.org/docs/api/web-contents#event-did-fail-load
  view.webContents.on('did-fail-load', (e, errorCode, errorDesc, validateUrl, isMainFrame) => {
    if (isMainFrame && errorCode < 0 && errorCode !== -3) {
      if (getWorkspace(workspace.id).active) {
        if (getWorkspace(workspace.id).active) {
          sendToAllWindows('update-loading', false);

          didFailLoad[workspace.id] = true;
          sendToAllWindows('update-did-fail-load', true);
        }
      }
    }

    // edge case to handle failed auth
    if (errorCode === -300 && view.webContents.getURL().length === 0) {
      view.webContents.loadURL(workspace.homeUrl || appJson.url);
    }
  });

  view.webContents.on('did-navigate', () => {
    if (getWorkspace(workspace.id).active) {
      sendToAllWindows('update-can-go-back', view.webContents.canGoBack());
      sendToAllWindows('update-can-go-forward', view.webContents.canGoForward());
    }
  });

  view.webContents.on('did-navigate-in-page', () => {
    if (getWorkspace(workspace.id).active) {
      sendToAllWindows('update-can-go-back', view.webContents.canGoBack());
      sendToAllWindows('update-can-go-forward', view.webContents.canGoForward());
    }
  });

  view.webContents.on('new-window', (e, nextUrl, frameName, disposition, options, additionalFeatures, referrer) => {
    const curDomain = extractDomain(workspace.homeUrl);
    const nextDomain = extractDomain(nextUrl);

    // load in same window
    if (
      nextDomain === 'accounts.google.com'
      || nextDomain === 'feedly.com'
    ) {
      e.preventDefault();
      view.webContents.loadURL(nextUrl);
      return;
    }

    // open new window normally if domain is not defined or same domain (about:)
    if (nextDomain === null || nextDomain === curDomain || nextUrl.indexOf('oauth') > -1) {
      e.preventDefault();
      const popupWin = new BrowserWindow({
        width: 500,
        height: 500,
        webPreferences: {
          nodeIntegration: false,
          contextIsolation: true,
          partition: shareWorkspaceBrowsingData ? 'persist:shared' : `persist:${workspace.id}`,
          preload: path.join(__dirname, '..', 'preload', 'view.js'),
        },
      });
      popupWin.loadURL(nextUrl, { httpReferrer: referrer });
      e.newGuest = popupWin;

      return;
    }

    // open external url in browser if domain doesn't match.
    shell.openExternal(nextUrl);
  });

  // Handle downloads
  // https://electronjs.org/docs/api/download-item
  view.webContents.session.on('will-download', (event, item) => {
    const {
      askForDownloadPath,
      downloadPath,
    } = getPreferences();

    // Set the save path, making Electron not to prompt a save dialog.
    if (!askForDownloadPath) {
      const finalFilePath = path.join(downloadPath, item.getFilename());
      if (!fsExtra.existsSync(finalFilePath)) {
        item.setSavePath(finalFilePath);
      }
    }
  });

  // Hide Electron from UA to improve compatibility
  // https://github.com/quanglam2807/webcatalog/issues/182
  let uaStr = view.webContents.getUserAgent();
  uaStr = uaStr.replace(` ${app.getName()}/${app.getVersion()}`, '');
  uaStr = uaStr.replace(` Electron/${process.versions.electron}`, '');
  view.webContents.setUserAgent(uaStr);

  // Unread count badge
  if (unreadCountBadge) {
    view.webContents.on('page-title-updated', (e, title) => {
      const itemCountRegex = /[([{](\d*?)[}\])]/;
      const match = itemCountRegex.exec(title);

      const incStr = match ? match[1] : '';
      const inc = parseInt(incStr, 10) || 0;
      badgeCounts[workspace.id] = inc;
      sendToAllWindows('set-workspace', workspace.id, {
        ...workspace,
        badgeCount: inc,
      });

      let count = 0;
      Object.values(badgeCounts).forEach((c) => {
        count += c;
      });

      app.setBadgeCount(count);
    });
  }

  // Find In Page
  view.webContents.on('found-in-page', (e, result) => {
    sendToAllWindows('update-find-in-page-matches', result.activeMatchOrdinal, result.matches);
  });

  // Link preview
  view.webContents.on('update-target-url', (e, url) => {
    view.webContents.send('update-target-url', url);
  });

  view.webContents.loadURL((rememberLastPageVisited && workspace.lastUrl)
    || workspace.homeUrl || appJson.url);

  views[workspace.id] = view;
};

const getView = (id) => views[id];

const setActiveView = (browserWindow, id) => {
  // stop find in page when switching workspaces
  const currentView = browserWindow.getBrowserView();
  currentView.webContents.stopFindInPage('clearSelection');
  browserWindow.send('close-find-in-page');

  const view = views[id];
  browserWindow.setBrowserView(view);

  const contentSize = browserWindow.getContentSize();

  const offsetTitlebar = process.platform !== 'darwin' || global.showSidebar || global.attachToMenubar ? 0 : 22;
  const x = global.showSidebar ? 68 : 0;
  const y = global.showNavigationBar ? 36 + offsetTitlebar : 0 + offsetTitlebar;

  view.setBounds({
    x,
    y,
    width: contentSize[0] - x,
    height: contentSize[1] - y,
  });
  view.setAutoResize({
    width: true,
    height: true,
  });

  sendToAllWindows('update-is-loading', view.webContents.isLoading());
  sendToAllWindows('update-did-fail-load', Boolean(didFailLoad[id]));
};

const removeView = (id) => {
  const view = views[id];
  session.fromPartition(`persist:${id}`).clearStorageData();
  view.destroy();
};

module.exports = {
  addView,
  getView,
  setActiveView,
  removeView,
};
<commit_msg>Fix new window handling error when workspace home URL is not set (#388)<commit_after>const {
  BrowserView,
  BrowserWindow,
  app,
  session,
  shell,
} = require('electron');
const path = require('path');
const fsExtra = require('fs-extra');

const appJson = require('../app.json');

const { getPreferences } = require('./preferences');
const {
  getWorkspace,
  setWorkspace,
} = require('./workspaces');

const sendToAllWindows = require('./send-to-all-windows');

const views = {};
const badgeCounts = {};
const didFailLoad = {};

const extractDomain = (fullUrl) => {
  const matches = fullUrl.match(/^https?:\/\/([^/?#]+)(?:[/?#]|$)/i);
  const domain = matches && matches[1];
  return domain ? domain.replace('www.', '') : null;
};

const addView = (browserWindow, workspace) => {
  const {
    rememberLastPageVisited,
    shareWorkspaceBrowsingData,
    unreadCountBadge,
  } = getPreferences();

  const view = new BrowserView({
    webPreferences: {
      nodeIntegration: false,
      contextIsolation: true,
      partition: shareWorkspaceBrowsingData ? 'persist:shared' : `persist:${workspace.id}`,
      preload: path.join(__dirname, '..', 'preload', 'view.js'),
    },
  });

  if (workspace.active) {
    browserWindow.setBrowserView(view);

    const contentSize = browserWindow.getContentSize();

    const offsetTitlebar = process.platform !== 'darwin' || global.showSidebar || global.attachToMenubar ? 0 : 22;
    const x = global.showSidebar ? 68 : 0;
    const y = global.showNavigationBar ? 36 + offsetTitlebar : 0 + offsetTitlebar;

    view.setBounds({
      x,
      y,
      width: contentSize[0] - x,
      height: contentSize[1] - y,
    });
    view.setAutoResize({
      width: true,
      height: true,
    });
  }

  view.webContents.on('did-start-loading', () => {
    if (getWorkspace(workspace.id).active) {
      didFailLoad[workspace.id] = false;
      sendToAllWindows('update-did-fail-load', false);
      sendToAllWindows('update-is-loading', true);
    }
  });

  view.webContents.on('did-stop-loading', () => {
    if (getWorkspace(workspace.id).active) {
      sendToAllWindows('update-is-loading', false);
    }

    const currentUrl = view.webContents.getURL();
    setWorkspace(workspace.id, {
      lastUrl: currentUrl,
    });
  });

  // https://electronjs.org/docs/api/web-contents#event-did-fail-load
  view.webContents.on('did-fail-load', (e, errorCode, errorDesc, validateUrl, isMainFrame) => {
    if (isMainFrame && errorCode < 0 && errorCode !== -3) {
      if (getWorkspace(workspace.id).active) {
        if (getWorkspace(workspace.id).active) {
          sendToAllWindows('update-loading', false);

          didFailLoad[workspace.id] = true;
          sendToAllWindows('update-did-fail-load', true);
        }
      }
    }

    // edge case to handle failed auth
    if (errorCode === -300 && view.webContents.getURL().length === 0) {
      view.webContents.loadURL(workspace.homeUrl || appJson.url);
    }
  });

  view.webContents.on('did-navigate', () => {
    if (getWorkspace(workspace.id).active) {
      sendToAllWindows('update-can-go-back', view.webContents.canGoBack());
      sendToAllWindows('update-can-go-forward', view.webContents.canGoForward());
    }
  });

  view.webContents.on('did-navigate-in-page', () => {
    if (getWorkspace(workspace.id).active) {
      sendToAllWindows('update-can-go-back', view.webContents.canGoBack());
      sendToAllWindows('update-can-go-forward', view.webContents.canGoForward());
    }
  });

  view.webContents.on('new-window', (e, nextUrl, frameName, disposition, options, additionalFeatures, referrer) => {
    const curDomain = extractDomain(workspace.homeUrl || appJson.url);
    const nextDomain = extractDomain(nextUrl);

    // load in same window
    if (
      nextDomain === 'accounts.google.com'
      || nextDomain === 'feedly.com'
    ) {
      e.preventDefault();
      view.webContents.loadURL(nextUrl);
      return;
    }

    // open new window normally if domain is not defined or same domain (about:)
    if (nextDomain === null || nextDomain === curDomain || nextUrl.indexOf('oauth') > -1) {
      e.preventDefault();
      const popupWin = new BrowserWindow({
        width: 500,
        height: 500,
        webPreferences: {
          nodeIntegration: false,
          contextIsolation: true,
          partition: shareWorkspaceBrowsingData ? 'persist:shared' : `persist:${workspace.id}`,
          preload: path.join(__dirname, '..', 'preload', 'view.js'),
        },
      });
      popupWin.loadURL(nextUrl, { httpReferrer: referrer });
      e.newGuest = popupWin;

      return;
    }

    // open external url in browser if domain doesn't match.
    shell.openExternal(nextUrl);
  });

  // Handle downloads
  // https://electronjs.org/docs/api/download-item
  view.webContents.session.on('will-download', (event, item) => {
    const {
      askForDownloadPath,
      downloadPath,
    } = getPreferences();

    // Set the save path, making Electron not to prompt a save dialog.
    if (!askForDownloadPath) {
      const finalFilePath = path.join(downloadPath, item.getFilename());
      if (!fsExtra.existsSync(finalFilePath)) {
        item.setSavePath(finalFilePath);
      }
    }
  });

  // Hide Electron from UA to improve compatibility
  // https://github.com/quanglam2807/webcatalog/issues/182
  let uaStr = view.webContents.getUserAgent();
  uaStr = uaStr.replace(` ${app.getName()}/${app.getVersion()}`, '');
  uaStr = uaStr.replace(` Electron/${process.versions.electron}`, '');
  view.webContents.setUserAgent(uaStr);

  // Unread count badge
  if (unreadCountBadge) {
    view.webContents.on('page-title-updated', (e, title) => {
      const itemCountRegex = /[([{](\d*?)[}\])]/;
      const match = itemCountRegex.exec(title);

      const incStr = match ? match[1] : '';
      const inc = parseInt(incStr, 10) || 0;
      badgeCounts[workspace.id] = inc;
      sendToAllWindows('set-workspace', workspace.id, {
        ...workspace,
        badgeCount: inc,
      });

      let count = 0;
      Object.values(badgeCounts).forEach((c) => {
        count += c;
      });

      app.setBadgeCount(count);
    });
  }

  // Find In Page
  view.webContents.on('found-in-page', (e, result) => {
    sendToAllWindows('update-find-in-page-matches', result.activeMatchOrdinal, result.matches);
  });

  // Link preview
  view.webContents.on('update-target-url', (e, url) => {
    view.webContents.send('update-target-url', url);
  });

  view.webContents.loadURL((rememberLastPageVisited && workspace.lastUrl)
    || workspace.homeUrl || appJson.url);

  views[workspace.id] = view;
};

const getView = (id) => views[id];

const setActiveView = (browserWindow, id) => {
  // stop find in page when switching workspaces
  const currentView = browserWindow.getBrowserView();
  currentView.webContents.stopFindInPage('clearSelection');
  browserWindow.send('close-find-in-page');

  const view = views[id];
  browserWindow.setBrowserView(view);

  const contentSize = browserWindow.getContentSize();

  const offsetTitlebar = process.platform !== 'darwin' || global.showSidebar || global.attachToMenubar ? 0 : 22;
  const x = global.showSidebar ? 68 : 0;
  const y = global.showNavigationBar ? 36 + offsetTitlebar : 0 + offsetTitlebar;

  view.setBounds({
    x,
    y,
    width: contentSize[0] - x,
    height: contentSize[1] - y,
  });
  view.setAutoResize({
    width: true,
    height: true,
  });

  sendToAllWindows('update-is-loading', view.webContents.isLoading());
  sendToAllWindows('update-did-fail-load', Boolean(didFailLoad[id]));
};

const removeView = (id) => {
  const view = views[id];
  session.fromPartition(`persist:${id}`).clearStorageData();
  view.destroy();
};

module.exports = {
  addView,
  getView,
  setActiveView,
  removeView,
};
<|endoftext|>