<commit_before>/**
 * Copyright 2016, Google, Inc.
 * 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';

// [START functions_sendgrid_setup]
const sendgrid = require('sendgrid');
const config = require('./config.json');
const uuid = require('uuid');

// Get a reference to the Cloud Storage component
const storage = require('@google-cloud/storage')();
// Get a reference to the BigQuery component
const bigquery = require('@google-cloud/bigquery')();
// [END functions_sendgrid_setup]

// [START functions_sendgrid_get_client]
/**
 * Returns a configured SendGrid client.
 *
 * @param {string} key Your SendGrid API key.
 * @returns {object} SendGrid client.
 */
function getClient(key) {
  console.log('getClient , key is ' + key);
  if (!key) {
    const error = new Error('SendGrid API key not provided. Make sure you have a "sg_key" property in your request querystring');
    error.code = 401;
    throw error;
  }

  // Using SendGrid's Node.js Library https://github.com/sendgrid/sendgrid-nodejs
  return sendgrid(key);
}
// [END functions_sendgrid_get_client]

// [START functions_get_payload]
/**
 * Constructs the SendGrid email request from the HTTP request body.
 *
 * @param {object} requestBody Cloud Function request body.
 * @param {string} data.to Email address of the recipient.
 * @param {string} data.from Email address of the sender.
 * @param {string} data.subject Email subject line.
 * @param {string} data.body Body of the email subject line.
 * @returns {object} Payload object.
 */
function getPayload(requestBody) {
  if (!requestBody.to) {
    const error = new Error('To email address not provided. Make sure you have a "to" property in your request');
    error.code = 400;
    throw error;
  } else if (!requestBody.from) {
    const error = new Error('From email address not provided. Make sure you have a "from" property in your request');
    error.code = 400;
    throw error;
  } else if (!requestBody.subject) {
    const error = new Error('Email subject line not provided. Make sure you have a "subject" property in your request');
    error.code = 400;
    throw error;
  } else if (!requestBody.body) {
    const error = new Error('Email content not provided. Make sure you have a "body" property in your request');
    error.code = 400;
    throw error;
  }

  return {
    personalizations: [
      {
        to: [
          {
            email: requestBody.to
          }
        ],
        subject: requestBody.subject
      }
    ],
    from: {
      email: requestBody.from
    },
    content: [
      {
        type: 'text/plain',
        value: requestBody.body
      }
    ]
  };
}
// [END functions_get_payload]

// [START functions_sendgrid_email]
/**
 * Send an email using SendGrid.
 *
 * Trigger this function by making a POST request with a payload to:
 * https://[YOUR_REGION].[YOUR_PROJECT_ID].cloudfunctions.net/sendEmail?sg_key=[YOUR_API_KEY]
 *
 * @example
 * curl -X POST "https://us-central1.your-project-id.cloudfunctions.net/sendEmail?sg_key=your_api_key" --data '{"to":"bob@email.com","from":"alice@email.com","subject":"Hello from Sendgrid!","body":"Hello World!"}' --header "Content-Type: application/json"
 *
 * @param {object} req Cloud Function request context.
 * @param {object} req.query The parsed querystring.
 * @param {string} req.query.sg_key Your SendGrid API key.
 * @param {object} req.body The request payload.
 * @param {string} req.body.to Email address of the recipient.
 * @param {string} req.body.from Email address of the sender.
 * @param {string} req.body.subject Email subject line.
 * @param {string} req.body.body Body of the email subject line.
 * @param {object} res Cloud Function response context.
 */
exports.sendgridEmail = function sendgridEmail(req, res) {
  return Promise.resolve()
    .then(() => {
      if (req.method === 'OPTIONS') {
        console.log(' OPTIONS');

        let headers = {};
        // IE8 does not allow domains to be specified, just the *
        // headers["Access-Control-Allow-Origin"] = req.headers.origin;
        headers["Access-Control-Allow-Origin"] = "*";
        headers["Access-Control-Allow-Methods"] = "POST, GET, PUT, DELETE, OPTIONS";
        headers["Access-Control-Allow-Credentials"] = false;
        headers["Access-Control-Max-Age"] = '86400'; // 24 hours
        headers["Access-Control-Allow-Headers"] = "access-control-allow-origin,content-type";
        res.writeHead(200, headers);
        res.end();
        console.log(' Return OPTIONS');
        return {};
      }
      else {
        console.log('POST');

        console.log(`sendgridEmail`);
        console.log(`sendgridEmail, req ` + Object.keys(req));
        console.log(`sendgridEmail, req.query ` + Object.keys(req.query));
        console.log(`sendgridEmail, req.method ` + req.method + '  ' + Object.keys(req.method));
        console.log(`sendgridEmail, req.body ` + Object.keys(req.body));
        if (!((req.method === 'POST' ) || (req.method === 'OPTIONS' ))) {
          const error = new Error('Only POST requests are accepted');
          error.code = 405;
          throw error;
        }

  // Get a SendGrid client

        const client = getClient(req.query.sg_key);
        console.log(`client - ` + client);

        // Build the SendGrid request to send email

        const request = client.emptyRequest({method: 'POST', path: '/v3/mail/send', body: getPayload(req.body)});
        console.log(`request - ` + request);

        // Make the request to SendGrid's API

        console.log(`Sending email to: ${req.body.to}`);
        return client.API(request)
          .then((response) => {
            console.log(``);
            if (response.statusCode < 200 || response.statusCode >= 400) {
              const error = Error(response.body);
              error.code = response.statusCode;
              throw error;
            }

            console.log(`Email sent to: ${req.body.to}`);
            // Forward the response back to the requester

            res.status(response.statusCode);
            if (response.headers['content-type']) {
              res.set('content-type', response.headers['content-type']);
            }
            if (response.headers['content-length']) {
              res.set('content-length', response.headers['content-length']);
            }
            if (response.headers['Access-Control-Allow-Origin']) {
              res.set('Access-Control-Allow-Origin', response.headers['Access-Control-Allow-Origin']);
            }
            if (response.headers['Access-Control-Allow-Methods']) {
              res.set('Access-Control-Allow-Methods', response.headers['Access-Control-Allow-Methods']);
            }
            if (response.headers['Access-Control-Allow-Headers']) {
              res.set('Access-Control-Allow-Headers', response.headers['Access-Control-Allow-Headers']);
            }
            if (response.body) {
              res.send(response.body);
            } else {
              res.end();
              console.log(' Return POST');
            }
          });
      }
    })
    .catch((err) => {
      console.error(err);
      const code = err.code || (err.response ? err.response.statusCode : 500) || 500;
      res.status(code).send(err);
      return Promise.reject(err);
    });
};
// [END functions_sendgrid_email]

// [START functions_sendgrid_verify_webhook]
/**
 * Verify that the webhook request came from sendgrid.
 *
 * @param {string} authorization The authorization header of the request, e.g. "Basic ZmdvOhJhcg=="
 */
function verifyWebhook(authorization) {
  const basicAuth = new Buffer(authorization.replace('Basic ', ''), 'base64').toString();
  const parts = basicAuth.split(':');
  if (parts[0] !== config.USERNAME || parts[1] !== config.PASSWORD) {
    const error = new Error('Invalid credentials');
    error.code = 401;
    throw error;
  }
}
// [END functions_sendgrid_verify_webhook]

// [START functions_sendgrid_fix_names]
/**
 * Recursively rename properties in to meet BigQuery field name requirements.
 *
 * @param {*} obj Value to examine.
 */
function fixNames(obj) {
  if (Array.isArray(obj)) {
    obj.forEach(fixNames);
  } else if (obj && typeof obj === 'object') {
    Object.keys(obj).forEach((key) => {
      const value = obj[key];
      fixNames(value);
      const fixedKey = key.replace('-', '_');
      if (fixedKey !== key) {
        obj[fixedKey] = value;
        delete obj[key];
      }
    });
  }
}
// [END functions_sendgrid_fix_names]

// [START functions_sendgrid_webhook]
/**
 * Receive a webhook from SendGrid.
 *
 * See https://sendgrid.com/docs/API_Reference/Webhooks/event.html
 *
 * @param {object} req Cloud Function request context.
 * @param {object} res Cloud Function response context.
 */
exports.sendgridWebhook = function sendgridWebhook(req, res) {
  return Promise.resolve()
    .then(() => {
      if (req.method !== 'POST') {
        const error = new Error('Only POST requests are accepted');
        error.code = 405;
        throw error;
      }

      verifyWebhook(req.get('authorization') || '');

      const events = req.body || [];

      // Make sure property names in the data meet BigQuery standards
      fixNames(events);

      // Generate newline-delimited JSON
      // See https://cloud.google.com/bigquery/data-formats#json_format
      const json = events.map((event) => JSON.stringify(event)).join('\n');

      // Upload a new file to Cloud Storage if we have events to save
      if (json.length) {
        const bucketName = config.EVENT_BUCKET;
        const unixTimestamp = new Date().getTime() * 1000;
        const filename = `${unixTimestamp}-${uuid.v4()}.json`;
        const file = storage.bucket(bucketName).file(filename);

        console.log(`Saving events to ${filename} in bucket ${bucketName}`);

        return file.save(json).then(() => {
          console.log(`JSON written to ${filename}`);
        });
      }
    })
    .then(() => res.status(200).end())
    .catch((err) => {
      console.error(err);
      res.status(err.code || 500).send(err);
      return Promise.reject(err);
    });
};
// [END functions_sendgrid_webhook]

// [START functions_sendgrid_get_table]
/**
 * Helper method to get a handle on a BigQuery table. Automatically creates the
 * dataset and table if necessary.
 */
function getTable() {
  const dataset = bigquery.dataset(config.DATASET);

  return dataset.get({autoCreate: true})
    .then(([dataset]) => dataset.table(config.TABLE).get({autoCreate: true}));
}
// [END functions_sendgrid_get_table]

// [START functions_sendgrid_load]
/**
 * Cloud Function triggered by Cloud Storage when a file is uploaded.
 *
 * @param {object} event The Cloud Functions event.
 * @param {object} event.data A Cloud Storage file object.
 * @param {string} event.data.bucket Name of the Cloud Storage bucket.
 * @param {string} event.data.name Name of the file.
 * @param {string} [event.data.timeDeleted] Time the file was deleted if this is a deletion event.
 * @see https://cloud.google.com/storage/docs/json_api/v1/objects#resource
 */
exports.sendgridLoad = function sendgridLoad(event) {
  const file = event.data;

  if (file.resourceState === 'not_exists') {
    // This was a deletion event, we don't want to process this
    return;
  }

  return Promise.resolve()
    .then(() => {
      if (!file.bucket) {
        throw new Error('Bucket not provided. Make sure you have a "bucket" property in your request');
      } else if (!file.name) {
        throw new Error('Filename not provided. Make sure you have a "name" property in your request');
      }

      return getTable();
    })
    .then(([table]) => {
      const fileObj = storage.bucket(file.bucket).file(file.name);
      console.log(`Starting job for ${file.name}`);
      const metadata = {
        autodetect: true,
        sourceFormat: 'NEWLINE_DELIMITED_JSON'
      };
      return table.import(fileObj, metadata);
    })
    .then(([job]) => job.promise())
    .then(() => console.log(`Job complete for ${file.name}`))
    .catch((err) => {
      console.log(`Job failed for ${file.name}`);
      return Promise.reject(err);
    });
};
// [END functions_sendgrid_load]
<commit_msg>send grid - send mail via gogle function<commit_after>/**
 * Copyright 2016, Google, Inc.
 * 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';

// [START functions_sendgrid_setup]
const sendgrid = require('sendgrid');
const config = require('./config.json');
const uuid = require('uuid');

// Get a reference to the Cloud Storage component
const storage = require('@google-cloud/storage')();
// Get a reference to the BigQuery component
const bigquery = require('@google-cloud/bigquery')();
// [END functions_sendgrid_setup]

// [START functions_sendgrid_get_client]
/**
 * Returns a configured SendGrid client.
 *
 * @param {string} key Your SendGrid API key.
 * @returns {object} SendGrid client.
 */
function getClient(key) {
  console.log('getClient , key is ' + key);
  if (!key) {
    const error = new Error('SendGrid API key not provided. Make sure you have a "sg_key" property in your request querystring');
    error.code = 401;
    throw error;
  }

  // Using SendGrid's Node.js Library https://github.com/sendgrid/sendgrid-nodejs
  return sendgrid(key);
}
// [END functions_sendgrid_get_client]

// [START functions_get_payload]
/**
 * Constructs the SendGrid email request from the HTTP request body.
 *
 * @param {object} requestBody Cloud Function request body.
 * @param {string} data.to Email address of the recipient.
 * @param {string} data.from Email address of the sender.
 * @param {string} data.subject Email subject line.
 * @param {string} data.body Body of the email subject line.
 * @returns {object} Payload object.
 */
function getPayload(requestBody) {
  if (!requestBody.to) {
    const error = new Error('To email address not provided. Make sure you have a "to" property in your request');
    error.code = 400;
    throw error;
  } else if (!requestBody.from) {
    const error = new Error('From email address not provided. Make sure you have a "from" property in your request');
    error.code = 400;
    throw error;
  } else if (!requestBody.subject) {
    const error = new Error('Email subject line not provided. Make sure you have a "subject" property in your request');
    error.code = 400;
    throw error;
  } else if (!requestBody.body) {
    const error = new Error('Email content not provided. Make sure you have a "body" property in your request');
    error.code = 400;
    throw error;
  }

  return {
    personalizations: [
      {
        to: [
          {
            email: requestBody.to
          }
        ],
        subject: requestBody.subject
      }
    ],
    from: {
      email: requestBody.from
    },
    content: [
      {
        type: 'text/plain',
        value: requestBody.body
      }
    ]
  };
}
// [END functions_get_payload]

// [START functions_sendgrid_email]
/**
 * Send an email using SendGrid.
 *
 * Trigger this function by making a POST request with a payload to:
 * https://[YOUR_REGION].[YOUR_PROJECT_ID].cloudfunctions.net/sendEmail?sg_key=[YOUR_API_KEY]
 *
 * @example
 * curl -X POST "https://us-central1.your-project-id.cloudfunctions.net/sendEmail?sg_key=your_api_key" --data '{"to":"bob@email.com","from":"alice@email.com","subject":"Hello from Sendgrid!","body":"Hello World!"}' --header "Content-Type: application/json"
 *
 * @param {object} req Cloud Function request context.
 * @param {object} req.query The parsed querystring.
 * @param {string} req.query.sg_key Your SendGrid API key.
 * @param {object} req.body The request payload.
 * @param {string} req.body.to Email address of the recipient.
 * @param {string} req.body.from Email address of the sender.
 * @param {string} req.body.subject Email subject line.
 * @param {string} req.body.body Body of the email subject line.
 * @param {object} res Cloud Function response context.
 */
exports.sendgridEmail = function sendgridEmail(req, res) {
  return Promise.resolve()
    .then(() => {
      if (req.method === 'OPTIONS') {
        console.log(' OPTIONS');

        let headers = {};
        // IE8 does not allow domains to be specified, just the *
        // headers["Access-Control-Allow-Origin"] = req.headers.origin;
        headers["Access-Control-Allow-Origin"] = "*";
        headers["Access-Control-Allow-Methods"] = "POST, GET, PUT, DELETE, OPTIONS";
        headers["Access-Control-Allow-Credentials"] = false;
        headers["Access-Control-Max-Age"] = '86400'; // 24 hours
        headers["Access-Control-Allow-Headers"] = "access-control-allow-origin,content-type";
        res.writeHead(200, headers);
        res.end();
        console.log(' Return OPTIONS');
        return {};
      }
      else {
        console.log('POST');

        console.log(`sendgridEmail`);
        console.log(`sendgridEmail, req ` + Object.keys(req));
        console.log(`sendgridEmail, req.query ` + Object.keys(req.query));
        console.log(`sendgridEmail, req.method ` + req.method + '  ' + Object.keys(req.method));
        console.log(`sendgridEmail, req.body ` + Object.keys(req.body));
        if (!((req.method === 'POST' ) || (req.method === 'OPTIONS' ))) {
          const error = new Error('Only POST requests are accepted');
          error.code = 405;
          throw error;
        }

        // Get a SendGrid client

        const client = getClient(req.query.sg_key);
        console.log(`client - ` + client);

        // Build the SendGrid request to send email

        const request = client.emptyRequest({method: 'POST', path: '/v3/mail/send', body: getPayload(req.body)});
        console.log(`request - ` + request);

        // Make the request to SendGrid's API

        console.log(`Sending email to: ${req.body.to}`);
        return client.API(request)
          .then((response) => {
            console.log(``);
            if (response.statusCode < 200 || response.statusCode >= 400) {
              const error = Error(response.body);
              error.code = response.statusCode;
              throw error;
            }

            console.log(`Email sent to: ${req.body.to}`);
            // Forward the response back to the requester

            res.status(response.statusCode);
            if (response.headers['content-type']) {
              res.set('content-type', response.headers['content-type']);
            }
            if (response.headers['content-length']) {
              res.set('content-length', response.headers['content-length']);
            }
            if (response.headers['Access-Control-Allow-Origin']) {
              res.set('Access-Control-Allow-Origin', response.headers['Access-Control-Allow-Origin']);
            }
            if (response.headers['Access-Control-Allow-Methods']) {
              res.set('Access-Control-Allow-Methods', response.headers['Access-Control-Allow-Methods']);
            }
            if (response.headers['Access-Control-Allow-Headers']) {
              res.set('Access-Control-Allow-Headers', response.headers['Access-Control-Allow-Headers']);
            }
            if (response.body) {
              res.send(response.body);
            } else {
              res.end();
              console.log(' Return POST');
            }
          });
      }
    })
    .catch((err) => {
      console.error(err);
      const code = err.code || (err.response ? err.response.statusCode : 500) || 500;
      res.status(code).send(err);
      return Promise.reject(err);
    });
};
// [END functions_sendgrid_email]

// [START functions_sendgrid_verify_webhook]
/**
 * Verify that the webhook request came from sendgrid.
 *
 * @param {string} authorization The authorization header of the request, e.g. "Basic ZmdvOhJhcg=="
 */
function verifyWebhook(authorization) {
  const basicAuth = new Buffer(authorization.replace('Basic ', ''), 'base64').toString();
  const parts = basicAuth.split(':');
  if (parts[0] !== config.USERNAME || parts[1] !== config.PASSWORD) {
    const error = new Error('Invalid credentials');
    error.code = 401;
    throw error;
  }
}
// [END functions_sendgrid_verify_webhook]

// [START functions_sendgrid_fix_names]
/**
 * Recursively rename properties in to meet BigQuery field name requirements.
 *
 * @param {*} obj Value to examine.
 */
function fixNames(obj) {
  if (Array.isArray(obj)) {
    obj.forEach(fixNames);
  } else if (obj && typeof obj === 'object') {
    Object.keys(obj).forEach((key) => {
      const value = obj[key];
      fixNames(value);
      const fixedKey = key.replace('-', '_');
      if (fixedKey !== key) {
        obj[fixedKey] = value;
        delete obj[key];
      }
    });
  }
}
// [END functions_sendgrid_fix_names]

// [START functions_sendgrid_webhook]
/**
 * Receive a webhook from SendGrid.
 *
 * See https://sendgrid.com/docs/API_Reference/Webhooks/event.html
 *
 * @param {object} req Cloud Function request context.
 * @param {object} res Cloud Function response context.
 */
exports.sendgridWebhook = function sendgridWebhook(req, res) {
  return Promise.resolve()
    .then(() => {
      if (req.method !== 'POST') {
        const error = new Error('Only POST requests are accepted');
        error.code = 405;
        throw error;
      }

      verifyWebhook(req.get('authorization') || '');

      const events = req.body || [];

      // Make sure property names in the data meet BigQuery standards
      fixNames(events);

      // Generate newline-delimited JSON
      // See https://cloud.google.com/bigquery/data-formats#json_format
      const json = events.map((event) => JSON.stringify(event)).join('\n');

      // Upload a new file to Cloud Storage if we have events to save
      if (json.length) {
        const bucketName = config.EVENT_BUCKET;
        const unixTimestamp = new Date().getTime() * 1000;
        const filename = `${unixTimestamp}-${uuid.v4()}.json`;
        const file = storage.bucket(bucketName).file(filename);

        console.log(`Saving events to ${filename} in bucket ${bucketName}`);

        return file.save(json).then(() => {
          console.log(`JSON written to ${filename}`);
        });
      }
    })
    .then(() => res.status(200).end())
    .catch((err) => {
      console.error(err);
      res.status(err.code || 500).send(err);
      return Promise.reject(err);
    });
};
// [END functions_sendgrid_webhook]

// [START functions_sendgrid_get_table]
/**
 * Helper method to get a handle on a BigQuery table. Automatically creates the
 * dataset and table if necessary.
 */
function getTable() {
  const dataset = bigquery.dataset(config.DATASET);

  return dataset.get({autoCreate: true})
    .then(([dataset]) => dataset.table(config.TABLE).get({autoCreate: true}));
}
// [END functions_sendgrid_get_table]

// [START functions_sendgrid_load]
/**
 * Cloud Function triggered by Cloud Storage when a file is uploaded.
 *
 * @param {object} event The Cloud Functions event.
 * @param {object} event.data A Cloud Storage file object.
 * @param {string} event.data.bucket Name of the Cloud Storage bucket.
 * @param {string} event.data.name Name of the file.
 * @param {string} [event.data.timeDeleted] Time the file was deleted if this is a deletion event.
 * @see https://cloud.google.com/storage/docs/json_api/v1/objects#resource
 */
exports.sendgridLoad = function sendgridLoad(event) {
  const file = event.data;

  if (file.resourceState === 'not_exists') {
    // This was a deletion event, we don't want to process this
    return;
  }

  return Promise.resolve()
    .then(() => {
      if (!file.bucket) {
        throw new Error('Bucket not provided. Make sure you have a "bucket" property in your request');
      } else if (!file.name) {
        throw new Error('Filename not provided. Make sure you have a "name" property in your request');
      }

      return getTable();
    })
    .then(([table]) => {
      const fileObj = storage.bucket(file.bucket).file(file.name);
      console.log(`Starting job for ${file.name}`);
      const metadata = {
        autodetect: true,
        sourceFormat: 'NEWLINE_DELIMITED_JSON'
      };
      return table.import(fileObj, metadata);
    })
    .then(([job]) => job.promise())
    .then(() => console.log(`Job complete for ${file.name}`))
    .catch((err) => {
      console.log(`Job failed for ${file.name}`);
      return Promise.reject(err);
    });
};
// [END functions_sendgrid_load]
<|endoftext|>


<commit_before>// Copyright (c) 2015 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.

/**
 * @param {!Object<string, *>} request
 * @param {function(*): void} sendResponse
 * @return {void}
 */
var handleRequest_ = function(request, sendResponse) {
  var promise;
  if (request.type === 'call_api') {
    promise = callApi_(resolveApi_(request.api), request.args, request.options);
  } else if (request.type === 'execute_script') {
    promise = executeScriptInTabs_(request.tabIds, request.code, request.options);
  } else if (request.type === 'insert_css') {
    promise = insertCssIntoTabs_(request.tabIds, request.css, request.options);
  } else {
    promise = Promise.reject('Unrecognized request type "' + request.type + '"');
  }

  promise.then(function(result) {
    sendResponse({success: true, result: result});
  }).catch(function(error) {
    sendResponse({success: false, error: error.message ? error.message : error});
  });
};

/**
 * @param {!string} apiName
 * @return {?Object} Resolved API object or null.
 */
var resolveApi_ = function(apiName) {
  var nameParts = apiName.split('.');
  var resolvedApi = window;
  for (var i = 0; i < nameParts.length; ++i) {
    resolvedApi = resolvedApi[nameParts[i]];
    if (!resolvedApi)
      return null;
  }
  return resolvedApi;
};

var BAD_API_INVOCATION_ERROR_RE_ =
    /^Error: Invocation of form (.+) doesn't match definition (.+)$/;

/**
 * @param {Object} api
 * @param {!Object<string, *>} args
 * @param {{timeout: number}} options
 * @return {!Promise<*>} Result of the API call.
 */
var callApi_ = function(api, args, options) {
  return new Promise(function(resolve, reject) {
    if (!api)
      return reject('No such API');

    // Complete the promise either via a callback or a timeout.
    var timedOut = false;
    var timeout = null;
    if (options.timeout) {
      timeout = setTimeout(function() {
        timedOut = true;
        reject('Timed out');
      }, options.timeout);
    }

    var callback = function(result) {
      if (!timedOut) {
        clearTimeout(timeout);
        if (chrome.runtime.lastError) {
          reject(chrome.runtime.lastError);
        } else {
          resolve(result);
        }
      }
    };

    try {
      // `args` is an array, so we must use `apply` to invoke the API. The API,
      // however, requires a callback as one extra argument: append it to `args`.
      // NOTE: Since the callback of this call will resolve/reject the outer
      // Promise, make sure the following is the last call of this function.
      api.apply(null, args.concat([callback]));
    } catch (error) {
      if (!timedOut) {
        clearTimeout(timeout);
        var match = BAD_API_INVOCATION_ERROR_RE_.exec(error);
        if (match) {
          // Massage this particularly frequent error message to be clearer.
          reject(
              'Wrong API arguments: expected ' + match[2] +
              ' but got ' + match[1] + ' <= ' + JSON.stringify(args));
        } else {
          reject(error);
        }
      }
    }
  });
};

/**
 * @param {!number} tabId
 * @param {!string} code
 * @param {{allFrames=: boolean, runAt=: string, timeout=: number}} options
 * @return {!Promise<*>}
 */
var executeScriptInTab_ = function(tabId, code, options) {
  // TODO(ussuri): Catch and return possible exceptions in the user's code.
  // The following didn't work:
  // code = 'try {' + code + '} catch (err) { console.log("CAUGHT"); err; }';
  var details = {
    code: code,
    allFrames: options.allFrames || false,
    runAt: options.runAt || 'document_idle'
  };

  return callApi_(chrome.tabs.executeScript, [tabId, details], options)
    .then(function(result) {
      return details.allFrames ? result: result[0];
    });
};

/**
 * @param {!(Array<number>|string)} tabIds
 * @param {!string} code
 * @param {{allFrames=: boolean, runAt=: string, timeout=: number}} options
 * @return {!Promise<Object<string, *>>}
 */
var executeScriptInTabs_ = function(tabIds, code, options) {
  return applyActionToTabs_(tabIds, executeScriptInTab_, [code, options]);
};

/**
 * @param {!number} tabId
 * @param {!string} css
 * @param {{allFrames: boolean, runAt: string, timeout: number}} options
 * @return {!Promise<*>}
 */
var insertCssIntoTab_ = function(tabId, css, options) {
  var details = {
    code: css,
    allFrames: options.allFrames || false,
    runAt: options.runAt || 'document_idle'
  };
  return callApi_(chrome.tabs.insertCSS, [tabId, details], options);
};

/**
 * @param {!(Array<number>|string)} tabIds
 * @param {!string} css
 * @param {{allFrames: boolean, runAt: string, timeout: number}} options
 * @return {!Promise<Object<string, *>>}
 */
var insertCssIntoTabs_ = function(tabIds, css, options) {
  return applyActionToTabs_(tabIds, insertCssIntoTab_, [css, options]);
};

/**
 * Return a map of {tabId -> result} pairs, with per-tab errors reported as
 * string results, but not flagged to the caller in an explicit way;
 * that is, a call will always "succeed" from the caller's perspective.
 *
 * @param {!(Array<number>|string)} tabIds
 * @param {!function(!number, !string, boolean=, string=)} action
 * @param {!Array<*>} args
 * @return {!Promise<Object<string, *>>}
 */
var applyActionToTabs_ = function(tabIds, action, args) {
  // TODO(ussuri): Somehow return mixed results/errors, while explicitly
  // indicating to callers that error(s) have occurred.
  return normalizeTabIds_(tabIds).then(function(nTabIds) {
    var results = {};

    var applyAction = function(tabId) {
      return action.apply(null, [tabId].concat(args))
        .then(function(result) {
          results[tabId] = result;
        }).catch(function(error) {
          results[tabId] =
              '<ERROR: ' + (error.message ? error.message : error) + '>';
        });
    };

    var promises = nTabIds.map(function(tabId) {
      return applyAction(tabId);
    });
    return Promise.all(promises).then(function(_) {
      return results;
    });
  });
};

/**
 * @param {!(Array<number>|string)} tabIds
 * @return {!Promise<number>}
 */
var normalizeTabIds_ = function(tabIds) {
  if (!tabIds || tabIds === 'all') {
    return getAllTabIds_(false);
  } else if (tabIds === 'window') {
    return getAllTabIds_(true);
  } else {
    return Promise.resolve(tabIds);
  }
};

/**
 * @param {boolean} currentWindow
 * @return {!Promise<number>} IDs of all the open tabs in all the windows.
 */
var getAllTabIds_ = function(currentWindow) {
  return new Promise(function(resolve, reject) {
    chrome.tabs.query({currentWindow: currentWindow}, function(tabs) {
      resolve(tabs.map(function(tab) { return tab.id; }));
    });
  });
};

/**
 * This is sent by our companion content script injected into a browser tab.
 */
chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse) {
    handleRequest_(request, sendResponse);
    // Indicate that the response is sent asynchronously.
    return true;
  }
);

/**
 * This is sent by a client to request some service from us.
 */
chrome.runtime.onMessageExternal.addListener(
  function(request, sender, sendResponse) {
    // TODO(ussuri): Verify the sender.
    handleRequest_(request, sendResponse);
    // Indicates that the response is sent asynchronously.
    return true;
  }
);

var WEB_SHELL_TAB_PROPS_ = {
  title: 'Console',
  url: '*://*/**/web_shell/index.html'
};
var HOSTED_WEB_SHELL_URL_ =
    'http://chromium.github.io/axiom/web_shell/index.html';
var activeWebShellTabIdx_ = -1;

/**
 * Clicking on the extension icon in the toolbar will:
 * - If no tabs with web shell are open, open a hosted shell in a new tab;
 * - If one or more tabs with the web shell are already open (e.g. a test
 *   and a hosted instances), then cycle through them.
 */
chrome.browserAction.onClicked.addListener(function() {
  chrome.tabs.query(WEB_SHELL_TAB_PROPS_, function(tabs) {
    if (tabs && tabs.length > 0) {
      ++activeWebShellTabIdx_;
      if (activeWebShellTabIdx_ >= tabs.length) {
        activeWebShellTabIdx_ = 0;
      }
      var tab = tabs[activeWebShellTabIdx_];
      lastFocusedWebShellTabUrl_ = tab.url;
      chrome.tabs.update(tab.id, {active: true}, function() {});
      chrome.windows.update(tab.windowId, {focused: true}, function() {});
    } else {
      chrome.tabs.create({url: HOSTED_WEB_SHELL_URL_, active: true});
    }
  });
});
<commit_msg>Fix last commit<commit_after>// Copyright (c) 2015 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.

/**
 * @param {!Object<string, *>} request
 * @param {function(*): void} sendResponse
 * @return {void}
 */
var handleRequest_ = function(request, sendResponse) {
  var promise;
  if (request.type === 'call_api') {
    promise = callApi_(resolveApi_(request.api), request.args, request.options);
  } else if (request.type === 'execute_script') {
    promise = executeScriptInTabs_(request.tabIds, request.code, request.options);
  } else if (request.type === 'insert_css') {
    promise = insertCssIntoTabs_(request.tabIds, request.css, request.options);
  } else {
    promise = Promise.reject('Unrecognized request type "' + request.type + '"');
  }

  promise.then(function(result) {
    sendResponse({success: true, result: result});
  }).catch(function(error) {
    sendResponse({success: false, error: error.message ? error.message : error});
  });
};

/**
 * @param {!string} apiName
 * @return {?Object} Resolved API object or null.
 */
var resolveApi_ = function(apiName) {
  var nameParts = apiName.split('.');
  var resolvedApi = window;
  for (var i = 0; i < nameParts.length; ++i) {
    resolvedApi = resolvedApi[nameParts[i]];
    if (!resolvedApi)
      return null;
  }
  return resolvedApi;
};

var BAD_API_INVOCATION_ERROR_RE_ =
    /^Error: Invocation of form (.+) doesn't match definition (.+)$/;

/**
 * @param {Object} api
 * @param {!Object<string, *>} args
 * @param {{timeout: number}} options
 * @return {!Promise<*>} Result of the API call.
 */
var callApi_ = function(api, args, options) {
  return new Promise(function(resolve, reject) {
    if (!api)
      return reject('No such API');

    // Complete the promise either via a callback or a timeout.
    var timedOut = false;
    var timeout = null;
    if (options.timeout) {
      timeout = setTimeout(function() {
        timedOut = true;
        reject('Timed out');
      }, options.timeout);
    }

    var callback = function(result) {
      if (!timedOut) {
        clearTimeout(timeout);
        if (chrome.runtime.lastError) {
          reject(chrome.runtime.lastError);
        } else {
          resolve(result);
        }
      }
    };

    try {
      // `args` is an array, so we must use `apply` to invoke the API. The API,
      // however, requires a callback as one extra argument: append it to `args`.
      // NOTE: Since the callback of this call will resolve/reject the outer
      // Promise, make sure the following is the last call of this function.
      api.apply(null, args.concat([callback]));
    } catch (error) {
      if (!timedOut) {
        clearTimeout(timeout);
        var match = BAD_API_INVOCATION_ERROR_RE_.exec(error);
        if (match) {
          // Massage this particularly frequent error message to be clearer.
          reject(
              'Wrong API arguments: expected ' + match[2] +
              ' but got ' + match[1] + ' <= ' + JSON.stringify(args));
        } else {
          reject(error);
        }
      }
    }
  });
};

/**
 * @param {!number} tabId
 * @param {!string} code
 * @param {{allFrames=: boolean, runAt=: string, timeout=: number}} options
 * @return {!Promise<*>}
 */
var executeScriptInTab_ = function(tabId, code, options) {
  // TODO(ussuri): Catch and return possible exceptions in the user's code.
  // The following didn't work:
  // code = 'try {' + code + '} catch (err) { console.log("CAUGHT"); err; }';
  var details = {
    code: code,
    allFrames: options.allFrames || false,
    runAt: options.runAt || 'document_idle'
  };

  return callApi_(chrome.tabs.executeScript, [tabId, details], options)
    .then(function(result) {
      return details.allFrames ? result: result[0];
    });
};

/**
 * @param {!(Array<number>|string)} tabIds
 * @param {!string} code
 * @param {{allFrames=: boolean, runAt=: string, timeout=: number}} options
 * @return {!Promise<Object<string, *>>}
 */
var executeScriptInTabs_ = function(tabIds, code, options) {
  return applyActionToTabs_(tabIds, executeScriptInTab_, [code, options]);
};

/**
 * @param {!number} tabId
 * @param {!string} css
 * @param {{allFrames: boolean, runAt: string, timeout: number}} options
 * @return {!Promise<*>}
 */
var insertCssIntoTab_ = function(tabId, css, options) {
  var details = {
    code: css,
    allFrames: options.allFrames || false,
    runAt: options.runAt || 'document_idle'
  };
  return callApi_(chrome.tabs.insertCSS, [tabId, details], options);
};

/**
 * @param {!(Array<number>|string)} tabIds
 * @param {!string} css
 * @param {{allFrames: boolean, runAt: string, timeout: number}} options
 * @return {!Promise<Object<string, *>>}
 */
var insertCssIntoTabs_ = function(tabIds, css, options) {
  return applyActionToTabs_(tabIds, insertCssIntoTab_, [css, options]);
};

/**
 * Return a map of {tabId -> result} pairs, with per-tab errors reported as
 * string results, but not flagged to the caller in an explicit way;
 * that is, a call will always "succeed" from the caller's perspective.
 *
 * @param {!(Array<number>|string)} tabIds
 * @param {!function(!number, !string, boolean=, string=)} action
 * @param {!Array<*>} args
 * @return {!Promise<Object<string, *>>}
 */
var applyActionToTabs_ = function(tabIds, action, args) {
  // TODO(ussuri): Somehow return mixed results/errors, while explicitly
  // indicating to callers that error(s) have occurred.
  return normalizeTabIds_(tabIds).then(function(nTabIds) {
    var results = {};

    var applyAction = function(tabId) {
      return action.apply(null, [tabId].concat(args))
        .then(function(result) {
          results[tabId] = result;
        }).catch(function(error) {
          results[tabId] =
              '<ERROR: ' + (error.message ? error.message : error) + '>';
        });
    };

    var promises = nTabIds.map(function(tabId) {
      return applyAction(tabId);
    });
    return Promise.all(promises).then(function(_) {
      return results;
    });
  });
};

/**
 * @param {!(Array<number>|string)} tabIds
 * @return {!Promise<number>}
 */
var normalizeTabIds_ = function(tabIds) {
  if (!tabIds || tabIds === 'all') {
    return getAllTabIds_(false);
  } else if (tabIds === 'window') {
    return getAllTabIds_(true);
  } else {
    return Promise.resolve(tabIds);
  }
};

/**
 * @param {boolean} currentWindow
 * @return {!Promise<number>} IDs of all the open tabs in all the windows.
 */
var getAllTabIds_ = function(currentWindow) {
  return new Promise(function(resolve, reject) {
    // NOTE: {currentWindow: false} means "other windows", but we want "all",
    // so use {}.
    var options = currentWindow ? {currentWindow: true} : {};
    chrome.tabs.query(options, function(tabs) {
      resolve(tabs.map(function(tab) { return tab.id; }));
    });
  });
};

/**
 * This is sent by our companion content script injected into a browser tab.
 */
chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse) {
    handleRequest_(request, sendResponse);
    // Indicate that the response is sent asynchronously.
    return true;
  }
);

/**
 * This is sent by a client to request some service from us.
 */
chrome.runtime.onMessageExternal.addListener(
  function(request, sender, sendResponse) {
    // TODO(ussuri): Verify the sender.
    handleRequest_(request, sendResponse);
    // Indicates that the response is sent asynchronously.
    return true;
  }
);

var WEB_SHELL_TAB_PROPS_ = {
  title: 'Console',
  url: '*://*/**/web_shell/index.html'
};
var HOSTED_WEB_SHELL_URL_ =
    'http://chromium.github.io/axiom/web_shell/index.html';
var activeWebShellTabIdx_ = -1;

/**
 * Clicking on the extension icon in the toolbar will:
 * - If no tabs with web shell are open, open a hosted shell in a new tab;
 * - If one or more tabs with the web shell are already open (e.g. a test
 *   and a hosted instances), then cycle through them.
 */
chrome.browserAction.onClicked.addListener(function() {
  chrome.tabs.query(WEB_SHELL_TAB_PROPS_, function(tabs) {
    if (tabs && tabs.length > 0) {
      ++activeWebShellTabIdx_;
      if (activeWebShellTabIdx_ >= tabs.length) {
        activeWebShellTabIdx_ = 0;
      }
      var tab = tabs[activeWebShellTabIdx_];
      lastFocusedWebShellTabUrl_ = tab.url;
      chrome.tabs.update(tab.id, {active: true}, function() {});
      chrome.windows.update(tab.windowId, {focused: true}, function() {});
    } else {
      chrome.tabs.create({url: HOSTED_WEB_SHELL_URL_, active: true});
    }
  });
});
<|endoftext|>


<commit_before>define(function (require) {

	var THREE = require("THREE");
	var PLAYER = require("player/playerSize");

	// Could be used for various types of bars or canvases
	// we want to have in the scene if somebody wants to make
	// it generic.
	return function PlayerMesh() {
		var self = this;

		var headMesh = createHead();
		var bodyMesh = createBody();
		var leftArm = createArm(1);
		var rightArm = createArm(-1);

		var jumpAngle = 0;
		var maxJumpAngle = 4 * Math.PI / 5;
		var jumpSpeed = maxJumpAngle / 300;

		var isMoving = false;
		var vy = 0;

		var swingAngle = 0;
		var maxSwingAngle = Math.PI / 2;
		var swingSpeed = 2 * Math.PI / 1000;
		var totalSwingTime = 0;

		self.update = function (entity, clock) {
			updatePosition(entity.pos());
			updateLook(entity.look());
			vy = entity.vy();
			health = entity.health();

			var dt = clock.dt();

			// Jump animation
			jumpAngle = clamp(jumpAngle + signum(vy) * dt * jumpSpeed, 0, maxJumpAngle);
			leftArm.rotation.z = jumpAngle;
			rightArm.rotation.z = -jumpAngle;

			// Arm swinging animation (as you walk)
			if (!isMoving) return;
			totalSwingTime += dt;
			var swingAngle = maxSwingAngle * sin(totalSwingTime * swingSpeed)
			if (swingAngle > -0.1 && swingAngle < 0.1) {
				isMoving = false;
				swingAngle = 0;
			}
			leftArm.rotation.x = -swingAngle;
			rightArm.rotation.x = swingAngle;
		};

		var previousPos = new THREE.Vector3(0, 0, 0);
		function updatePosition(newPos) {
			var diffX = previousPos.x - newPos.x;
			var diffZ = previousPos.z - newPos.z;
			if (abs(diffX) > 0.01 || abs(diffZ) > 0.01) {
				isMoving = true;
			}
			previousPos = newPos;
		};

		function updateLook(newLook) {
			function lookAt(obj, pos, x, y, z) {
				var target = new THREE.Vector3(
					pos.x + x,
					pos.y + y,
					pos.z + z
				);
				obj.lookAt(target);
			}
			lookAt(headMesh, headMesh.position, 0, newLook.y, 1);
		};

		// Model/geometry
		function createHitbox() {
			var hitboxMaterial = new THREE.MeshBasicMaterial({
				color: 0xff0000,
				wireframe: true
			});
			var he = PLAYER.HALF_EXTENTS;
			var hitboxGeometry = new THREE.CubeGeometry(he.x * 2, he.y * 2, he.z * 2);
			var hitboxMesh = new THREE.Mesh(hitboxGeometry, hitboxMaterial);
			return hitboxMesh;
		}

		function createHead() {
			var headMat = new THREE.MeshBasicMaterial({
				color: 0x0000ff
			});
			var faceMat = new THREE.MeshBasicMaterial({
				color: 0x00ff00
			});

			var geometry = new THREE.CubeGeometry(0.3, 0.3, 0.3);
			geometry.materials = [headMat, faceMat];
			geometry.faces[0].materialIndex = 0;
			geometry.faces[1].materialIndex = 0;
			geometry.faces[2].materialIndex = 0;
			geometry.faces[3].materialIndex = 0;
			geometry.faces[4].materialIndex = 1;
			geometry.faces[5].materialIndex = 0;

			var mesh = new THREE.Mesh(geometry, new THREE.MeshFaceMaterial(geometry.materials));

			var peh = PLAYER.EYE_HEIGHT;
			var pbh = PLAYER.BODY_HEIGHT;
			mesh.position.y = peh - pbh / 2;

			return mesh;
		}

		function createBody() {
			var material = new THREE.MeshBasicMaterial({
				color: 0x0000ff,
				wireframe: true
			});
			var geometry = new THREE.CubeGeometry(0.6, PLAYER.BODY_HEIGHT, 0.4);
			var mesh = new THREE.Mesh(geometry, material);
			mesh.position.y = 0;
			return mesh;
		}

		function createArm(offset) {
			var armMat = new THREE.MeshBasicMaterial({
				color: 0xff0000,
				wireframe: true
			});
			var geometry = new THREE.CubeGeometry(0.2, 0.6, 0.2);
			var mesh = new THREE.Mesh(geometry, armMat);
			mesh.position.y = -0.3;

			var arm = new THREE.Object3D();
			arm.add(mesh);
			arm.position.x = offset * 0.4;
			arm.position.y = PLAYER.BODY_HEIGHT / 2;
			return arm;
		}

		self.meshes = function () {
			return [headMesh, bodyMesh, leftArm, rightArm];
		};
	}
});<commit_msg>Not relevent comment.<commit_after>define(function (require) {

var THREE = require("THREE");
var PLAYER = require("player/playerSize");

return function PlayerMesh() {
	var self = this;

	var headMesh = createHead();
	var bodyMesh = createBody();
	var leftArm = createArm(1);
	var rightArm = createArm(-1);

	var jumpAngle = 0;
	var maxJumpAngle = 4 * Math.PI / 5;
	var jumpSpeed = maxJumpAngle / 300;

	var isMoving = false;
	var vy = 0;

	var swingAngle = 0;
	var maxSwingAngle = Math.PI / 2;
	var swingSpeed = 2 * Math.PI / 1000;
	var totalSwingTime = 0;

	self.update = function (entity, clock) {
		updatePosition(entity.pos());
		updateLook(entity.look());
		vy = entity.vy();
		health = entity.health();

		var dt = clock.dt();

		// Jump animation
		jumpAngle = clamp(jumpAngle + signum(vy) * dt * jumpSpeed, 0, maxJumpAngle);
		leftArm.rotation.z = jumpAngle;
		rightArm.rotation.z = -jumpAngle;

		// Arm swinging animation (as you walk)
		if (!isMoving) return;
		totalSwingTime += dt;
		var swingAngle = maxSwingAngle * sin(totalSwingTime * swingSpeed)
		if (swingAngle > -0.1 && swingAngle < 0.1) {
			isMoving = false;
			swingAngle = 0;
		}
		leftArm.rotation.x = -swingAngle;
		rightArm.rotation.x = swingAngle;
	};

	var previousPos = new THREE.Vector3(0, 0, 0);
	function updatePosition(newPos) {
		var diffX = previousPos.x - newPos.x;
		var diffZ = previousPos.z - newPos.z;
		if (abs(diffX) > 0.01 || abs(diffZ) > 0.01) {
			isMoving = true;
		}
		previousPos = newPos;
	};

	function updateLook(newLook) {
		function lookAt(obj, pos, x, y, z) {
			var target = new THREE.Vector3(
				pos.x + x,
				pos.y + y,
				pos.z + z
			);
			obj.lookAt(target);
		}
		lookAt(headMesh, headMesh.position, 0, newLook.y, 1);
	};

	// Model/geometry
	function createHitbox() {
		var hitboxMaterial = new THREE.MeshBasicMaterial({
			color: 0xff0000,
			wireframe: true
		});
		var he = PLAYER.HALF_EXTENTS;
		var hitboxGeometry = new THREE.CubeGeometry(he.x * 2, he.y * 2, he.z * 2);
		var hitboxMesh = new THREE.Mesh(hitboxGeometry, hitboxMaterial);
		return hitboxMesh;
	}

	function createHead() {
		var headMat = new THREE.MeshBasicMaterial({
			color: 0x0000ff
		});
		var faceMat = new THREE.MeshBasicMaterial({
			color: 0x00ff00
		});

		var geometry = new THREE.CubeGeometry(0.3, 0.3, 0.3);
		geometry.materials = [headMat, faceMat];
		geometry.faces[0].materialIndex = 0;
		geometry.faces[1].materialIndex = 0;
		geometry.faces[2].materialIndex = 0;
		geometry.faces[3].materialIndex = 0;
		geometry.faces[4].materialIndex = 1;
		geometry.faces[5].materialIndex = 0;

		var mesh = new THREE.Mesh(geometry, new THREE.MeshFaceMaterial(geometry.materials));

		var peh = PLAYER.EYE_HEIGHT;
		var pbh = PLAYER.BODY_HEIGHT;
		mesh.position.y = peh - pbh / 2;

		return mesh;
	}

	function createBody() {
		var material = new THREE.MeshBasicMaterial({
			color: 0x0000ff,
			wireframe: true
		});
		var geometry = new THREE.CubeGeometry(0.6, PLAYER.BODY_HEIGHT, 0.4);
		var mesh = new THREE.Mesh(geometry, material);
		mesh.position.y = 0;
		return mesh;
	}

	function createArm(offset) {
		var armMat = new THREE.MeshBasicMaterial({
			color: 0xff0000,
			wireframe: true
		});
		var geometry = new THREE.CubeGeometry(0.2, 0.6, 0.2);
		var mesh = new THREE.Mesh(geometry, armMat);
		mesh.position.y = -0.3;

		var arm = new THREE.Object3D();
		arm.add(mesh);
		arm.position.x = offset * 0.4;
		arm.position.y = PLAYER.BODY_HEIGHT / 2;
		return arm;
	}

	self.meshes = function () {
		return [headMesh, bodyMesh, leftArm, rightArm];
	};
}
});
<|endoftext|>


<commit_before>import _ from 'underscore';
import Backbone from 'backbone';

import { apiRoot } from 'girder/rest';

/**
 * The EventStream type wraps window.EventSource to listen to the unified
 * per-user event channel endpoint using the SSE protocol. When events are
 * received on the SSE channel, this triggers a Backbone event of the form
 * 'g:event.<type>' where <type> is the value of the event type field.
 * Listeners can bind to specific event types on the channel.
 */
function EventStream(settings) {
    var defaults = {
        timeout: null,
        streamPath: '/notification/stream'
    };

    this.settings = _.extend(defaults, settings);

    return _.extend(this, Backbone.Events);
}

var prototype = EventStream.prototype;

/*
 * This method is used to kill the event stream socket when the girder tab is not
 * visible using window.requestAnimationFrame. It creates a dead man switch to
 * close the frame after 5 seconds if requestAnimationFrame is not called in that time.
 */
prototype._heartbeat = function () {
    if (!this._stopHeartbeat) {
        window.requestAnimationFrame(() => { this._heartbeat(); });
    }

    if (this._eventSource) {
        window.clearTimeout(this._closeTimeout);
        this._closeTimeout = window.setTimeout(() => {
            this._eventSource.close();
            this._eventSource = null;
        }, 5000);
    } else {
        this.open();
    }
};

prototype.open = function () {
    if (window.EventSource) {
        var stream = this,
            url = apiRoot + this.settings.streamPath;

        if (this.settings.timeout) {
            url += '?timeout=' + this.settings.timeout;
        }

        this._eventSource = new window.EventSource(url);

        this._eventSource.onmessage = function (e) {
            var obj;
            try {
                obj = window.JSON.parse(e.data);
            } catch (err) {
                console.error('Invalid JSON from SSE stream: ' + e.data + ',' + err);
                stream.trigger('g:error', e);
                return;
            }
            stream.trigger('g:event.' + obj.type, obj);
        };
        this._stopHeartbeat = false;
        this._heartbeat();
    } else {
        console.error('EventSource is not supported on this platform.');
    }
};

prototype.close = function () {
    this._stopHeartbeat = true;
    if (this._eventSource) {
        this._eventSource.close();
        this._eventSource = null;
    }
};

var eventStream = new EventStream();

export default eventStream;
<commit_msg>Store last sse message timestamp in localStorage<commit_after>import _ from 'underscore';
import Backbone from 'backbone';

import { apiRoot } from 'girder/rest';

/**
 * The EventStream type wraps window.EventSource to listen to the unified
 * per-user event channel endpoint using the SSE protocol. When events are
 * received on the SSE channel, this triggers a Backbone event of the form
 * 'g:event.<type>' where <type> is the value of the event type field.
 * Listeners can bind to specific event types on the channel.
 */
function EventStream(settings) {
    var defaults = {
        timeout: null,
        streamPath: '/notification/stream'
    };

    this.settings = _.extend(defaults, settings);

    return _.extend(this, Backbone.Events);
}

var prototype = EventStream.prototype;

/*
 * This method is used to kill the event stream socket when the girder tab is not
 * visible using window.requestAnimationFrame. It creates a dead man switch to
 * close the frame after 5 seconds if requestAnimationFrame is not called in that time.
 */
prototype._heartbeat = function () {
    if (!this._stopHeartbeat) {
        window.requestAnimationFrame(() => { this._heartbeat(); });
    }

    if (this._eventSource) {
        window.clearTimeout(this._closeTimeout);
        this._closeTimeout = window.setTimeout(() => {
            this._eventSource.close();
            this._eventSource = null;
        }, 5000);
    } else {
        this.open();
    }
};

prototype.open = function () {
    if (window.EventSource) {
        var stream = this,
            url = apiRoot + this.settings.streamPath,
            params = {};

        if (this.settings.timeout) {
            params.timeout = this.settings.timeout;
        }

        // Set the "since" argument to filter out notifications
        // that have already been sent to this client.
        var since = parseInt(localStorage.getItem('sseTimestamp'));
        if (since >= 0) {
            params.since = since;
        }

        url += '?' + $.param(params);
        this._eventSource = new window.EventSource(url);

        this._eventSource.onmessage = function (e) {
            var obj;
            try {
                obj = window.JSON.parse(e.data);
            } catch (err) {
                console.error('Invalid JSON from SSE stream: ' + e.data + ',' + err);
                stream.trigger('g:error', e);
                return;
            }
            localStorage.setItem('sseTimestamp', obj._girderTime);
            stream.trigger('g:event.' + obj.type, obj);
        };
        this._stopHeartbeat = false;
        this._heartbeat();
    } else {
        console.error('EventSource is not supported on this platform.');
    }
};

prototype.close = function () {
    this._stopHeartbeat = true;
    if (this._eventSource) {
        this._eventSource.close();
        this._eventSource = null;
    }
};

var eventStream = new EventStream();

export default eventStream;
<|endoftext|>


<commit_before>// Copyright 2006 The Closure Library Authors. 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.

/**
 * @fileoverview This event wrapper will dispatch an event when the user uses
 * the mouse wheel to scroll an element. You can get the direction by checking
 * the deltaX and deltaY properties of the event.
 *
 * This class aims to smooth out inconsistencies between browser platforms with
 * regards to mousewheel events, but we do not cover every possible
 * software/hardware combination out there, some of which occasionally produce
 * very large deltas in mousewheel events. If your application wants to guard
 * against extremely large deltas, use the setMaxDeltaX and setMaxDeltaY APIs
 * to set maximum values that make sense for your application.
 *
 * @author arv@google.com (Erik Arvidsson)
 * @see ../demos/mousewheelhandler.html
 */

goog.provide('goog.events.MouseWheelEvent');
goog.provide('goog.events.MouseWheelHandler');
goog.provide('goog.events.MouseWheelHandler.EventType');

goog.require('goog.dom.DomHelper');
goog.require('goog.events');
goog.require('goog.events.BrowserEvent');
goog.require('goog.events.EventTarget');
goog.require('goog.math');
goog.require('goog.style');
goog.require('goog.userAgent');

/**
 * This event handler allows you to catch mouse wheel events in a consistent
 * manner.
 * @param {Element|Document} element The element to listen to the mouse wheel
 *     event on.
 * @constructor
 * @extends {goog.events.EventTarget}
 */
goog.events.MouseWheelHandler = function(element) {
  goog.events.EventTarget.call(this);

  /**
   * This is the element that we will listen to the real mouse wheel events on.
   * @type {Element|Document}
   * @private
   */
  this.element_ = element;

  /**
   * True if the element we are listening on is RTL, false otherwise.
   * @type {boolean}
   * @private
   */
  this.isRtl_ = goog.style.isRightToLeft(
      goog.dom.isElement(this.element_) ?
          (/** @type {Element} */ this.element_) :
          (/** @type {Document} */ this.element_).body);

  var type = goog.userAgent.GECKO ? 'DOMMouseScroll' : 'mousewheel';

  /**
   * The key returned from the goog.events.listen.
   * @type {?number}
   * @private
   */
  this.listenKey_ = goog.events.listen(this.element_, type, this);
};
goog.inherits(goog.events.MouseWheelHandler, goog.events.EventTarget);

/**
 * Enum type for the events fired by the mouse wheel handler.
 * @enum {string}
 */
goog.events.MouseWheelHandler.EventType = {
  MOUSEWHEEL: 'mousewheel'
};

/**
 * Optional maximum magnitude for x delta on each mousewheel event.
 * @type {number|undefined}
 * @private
 */
goog.events.MouseWheelHandler.prototype.maxDeltaX_;

/**
 * Optional maximum magnitude for y delta on each mousewheel event.
 * @type {number|undefined}
 * @private
 */
goog.events.MouseWheelHandler.prototype.maxDeltaY_;

/**
 * @param {number} maxDeltaX Maximum magnitude for x delta on each mousewheel
 *     event. Should be non-negative.
 */
goog.events.MouseWheelHandler.prototype.setMaxDeltaX = function(maxDeltaX) {
  this.maxDeltaX_ = maxDeltaX;
};

/**
 * @param {number} maxDeltaY Maximum magnitude for y delta on each mousewheel
 *     event. Should be non-negative.
 */
goog.events.MouseWheelHandler.prototype.setMaxDeltaY = function(maxDeltaY) {
  this.maxDeltaY_ = maxDeltaY;
};

/**
 * Handles the events on the element.
 * @param {goog.events.BrowserEvent} e The underlying browser event.
 */
goog.events.MouseWheelHandler.prototype.handleEvent = function(e) {
  var deltaX = 0;
  var deltaY = 0;
  var detail = 0;
  var be = e.getBrowserEvent();
  if (be.type == 'mousewheel') {
    var wheelDeltaScaleFactor = 1;
    if (goog.userAgent.IE ||
        goog.userAgent.WEBKIT &&
        (goog.userAgent.WINDOWS || goog.userAgent.isVersion('532.0'))) {
      // In IE we get a multiple of 120; we adjust to a multiple of 3 to
      // represent number of lines scrolled (like Gecko).
      // Newer versions of Webkit match IE behavior, and WebKit on
      // Windows also matches IE behavior.
      // See bug https://bugs.webkit.org/show_bug.cgi?id=24368
      wheelDeltaScaleFactor = 40;
    }

    detail = goog.events.MouseWheelHandler.smartScale_(
        -be.wheelDelta, wheelDeltaScaleFactor);
    if (goog.isDef(be.wheelDeltaX)) {
      // Webkit has two properties to indicate directional scroll, and
      // can scroll both directions at once.
      deltaX = goog.events.MouseWheelHandler.smartScale_(
          -be.wheelDeltaX, wheelDeltaScaleFactor);
      deltaY = goog.events.MouseWheelHandler.smartScale_(
          -be.wheelDeltaY, wheelDeltaScaleFactor);
    } else {
      deltaY = detail;
    }

    // Historical note: Opera (pre 9.5) used to negate the detail value.
  } else { // Gecko
    // Gecko returns multiple of 3 (representing the number of lines scrolled)
    detail = be.detail;

    // Gecko sometimes returns really big values if the user changes settings to
    // scroll a whole page per scroll
    if (detail > 100) {
      detail = 3;
    } else if (detail < -100) {
      detail = -3;
    }

    // Firefox 3.1 adds an axis field to the event to indicate direction of
    // scroll.  See https://developer.mozilla.org/en/Gecko-Specific_DOM_Events
    if (goog.isDef(be.axis) && be.axis === be.HORIZONTAL_AXIS) {
      deltaX = detail;
    } else {
      deltaY = detail;
    }
  }

  if (goog.isNumber(this.maxDeltaX_)) {
    deltaX = goog.math.clamp(deltaX, -this.maxDeltaX_, this.maxDeltaX_);
  }
  if (goog.isNumber(this.maxDeltaY_)) {
    deltaY = goog.math.clamp(deltaY, -this.maxDeltaY_, this.maxDeltaY_);
  }
  // Don't clamp 'detail', since it could be ambiguous which axis it refers to
  // and because it's informally deprecated anyways.

  // For horizontal scrolling we need to flip the value for RTL grids.
  if (this.isRtl_) {
    deltaX = -deltaX;
  }
  var newEvent = new goog.events.MouseWheelEvent(detail, be, deltaX, deltaY);
  try {
    this.dispatchEvent(newEvent);
  } finally {
    newEvent.dispose();
  }
};

/**
 * Helper for scaling down a mousewheel delta by a scale factor, if appropriate.
 * @param {number} mouseWheelDelta Delta from a mouse wheel event. Expected to
 *     be an integer.
 * @param {number} scaleFactor Factor to scale the delta down by. Expected to
 *     be an integer.
 * @return {number} Scaled-down delta value, or the original delta if the
 *     scaleFactor does not appear to be applicable.
 * @private
 */
goog.events.MouseWheelHandler.smartScale_ = function(mouseWheelDelta,
    scaleFactor) {
  // The basic problem here is that in Webkit on Mac and Linux, we can get two
  // very different types of mousewheel events: from continuous devices
  // (touchpads, Mighty Mouse) or non-continuous devices (normal wheel mice).
  //
  // Non-continuous devices in Webkit get their wheel deltas scaled up to
  // behave like IE. Continuous devices return much smaller unscaled values
  // (which most of the time will not be cleanly divisible by the IE scale
  // factor), so we should not try to normalize them down.
  //
  // Detailed discussion:
  //   https://bugs.webkit.org/show_bug.cgi?id=29601
  //   http://trac.webkit.org/browser/trunk/WebKit/chromium/src/mac/WebInputEventFactory.mm#L1063
  if (goog.userAgent.WEBKIT &&
      (goog.userAgent.MAC || goog.userAgent.LINUX) &&
      (mouseWheelDelta % scaleFactor) != 0) {
    return mouseWheelDelta;
  } else {
    return mouseWheelDelta / scaleFactor;
  }
};

/** @override */
goog.events.MouseWheelHandler.prototype.disposeInternal = function() {
  goog.events.MouseWheelHandler.superClass_.disposeInternal.call(this);
  goog.events.unlistenByKey(this.listenKey_);
  delete this.listenKey_;
};

/**
 * A base class for mouse wheel events. This is used with the
 * MouseWheelHandler.
 *
 * @param {number} detail The number of rows the user scrolled.
 * @param {Event} browserEvent Browser event object.
 * @param {number} deltaX The number of rows the user scrolled in the X
 *     direction.
 * @param {number} deltaY The number of rows the user scrolled in the Y
 *     direction.
 * @constructor
 * @extends {goog.events.BrowserEvent}
 */
goog.events.MouseWheelEvent = function(detail, browserEvent, deltaX, deltaY) {
  goog.events.BrowserEvent.call(this, browserEvent);

  this.type = goog.events.MouseWheelHandler.EventType.MOUSEWHEEL;

  /**
   * The number of lines the user scrolled
   * @type {number}
   * NOTE: Informally deprecated. Use deltaX and deltaY instead, they provide
   * more information.
   */
  this.detail = detail;

  /**
   * The number of "lines" scrolled in the X direction.
   *
   * Note that not all browsers provide enough information to distinguish
   * horizontal and vertical scroll events, so for these unsupported browsers,
   * we will always have a deltaX of 0, even if the user scrolled their mouse
   * wheel or trackpad sideways.
   *
   * Currently supported browsers are Webkit and Firefox 3.1 or later.
   *
   * @type {number}
   */
  this.deltaX = deltaX;

  /**
   * The number of lines scrolled in the Y direction.
   * @type {number}
   */
  this.deltaY = deltaY;
};
goog.inherits(goog.events.MouseWheelEvent, goog.events.BrowserEvent);
<commit_msg><commit_after>// Copyright 2006 The Closure Library Authors. 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.

/**
 * @fileoverview This event wrapper will dispatch an event when the user uses
 * the mouse wheel to scroll an element. You can get the direction by checking
 * the deltaX and deltaY properties of the event.
 *
 * This class aims to smooth out inconsistencies between browser platforms with
 * regards to mousewheel events, but we do not cover every possible
 * software/hardware combination out there, some of which occasionally produce
 * very large deltas in mousewheel events. If your application wants to guard
 * against extremely large deltas, use the setMaxDeltaX and setMaxDeltaY APIs
 * to set maximum values that make sense for your application.
 *
 * @author arv@google.com (Erik Arvidsson)
 * @see ../demos/mousewheelhandler.html
 */

goog.provide('goog.events.MouseWheelEvent');
goog.provide('goog.events.MouseWheelHandler');
goog.provide('goog.events.MouseWheelHandler.EventType');

goog.require('goog.dom.DomHelper');
goog.require('goog.events');
goog.require('goog.events.BrowserEvent');
goog.require('goog.events.EventTarget');
goog.require('goog.math');
goog.require('goog.style');
goog.require('goog.userAgent');

/**
 * This event handler allows you to catch mouse wheel events in a consistent
 * manner.
 * @param {Element|Document} element The element to listen to the mouse wheel
 *     event on.
 * @constructor
 * @extends {goog.events.EventTarget}
 */
goog.events.MouseWheelHandler = function(element) {
  goog.events.EventTarget.call(this);

  /**
   * This is the element that we will listen to the real mouse wheel events on.
   * @type {Element|Document}
   * @private
   */
  this.element_ = element;

  /**
   * True if the element exists and is RTL, false otherwise.
   * @type {boolean}
   * @private
   */
  this.isRtl_ = this.element_ ?
      (goog.style.isRightToLeft(
          goog.dom.isElement(this.element_) ?
              (/** @type {Element} */ this.element_) :
              (/** @type {Document} */ this.element_).body)) :
      false;

  var type = goog.userAgent.GECKO ? 'DOMMouseScroll' : 'mousewheel';

  /**
   * The key returned from the goog.events.listen.
   * @type {?number}
   * @private
   */
  this.listenKey_ = goog.events.listen(this.element_, type, this);
};
goog.inherits(goog.events.MouseWheelHandler, goog.events.EventTarget);

/**
 * Enum type for the events fired by the mouse wheel handler.
 * @enum {string}
 */
goog.events.MouseWheelHandler.EventType = {
  MOUSEWHEEL: 'mousewheel'
};

/**
 * Optional maximum magnitude for x delta on each mousewheel event.
 * @type {number|undefined}
 * @private
 */
goog.events.MouseWheelHandler.prototype.maxDeltaX_;

/**
 * Optional maximum magnitude for y delta on each mousewheel event.
 * @type {number|undefined}
 * @private
 */
goog.events.MouseWheelHandler.prototype.maxDeltaY_;

/**
 * @param {number} maxDeltaX Maximum magnitude for x delta on each mousewheel
 *     event. Should be non-negative.
 */
goog.events.MouseWheelHandler.prototype.setMaxDeltaX = function(maxDeltaX) {
  this.maxDeltaX_ = maxDeltaX;
};

/**
 * @param {number} maxDeltaY Maximum magnitude for y delta on each mousewheel
 *     event. Should be non-negative.
 */
goog.events.MouseWheelHandler.prototype.setMaxDeltaY = function(maxDeltaY) {
  this.maxDeltaY_ = maxDeltaY;
};

/**
 * Handles the events on the element.
 * @param {goog.events.BrowserEvent} e The underlying browser event.
 */
goog.events.MouseWheelHandler.prototype.handleEvent = function(e) {
  var deltaX = 0;
  var deltaY = 0;
  var detail = 0;
  var be = e.getBrowserEvent();
  if (be.type == 'mousewheel') {
    var wheelDeltaScaleFactor = 1;
    if (goog.userAgent.IE ||
        goog.userAgent.WEBKIT &&
        (goog.userAgent.WINDOWS || goog.userAgent.isVersion('532.0'))) {
      // In IE we get a multiple of 120; we adjust to a multiple of 3 to
      // represent number of lines scrolled (like Gecko).
      // Newer versions of Webkit match IE behavior, and WebKit on
      // Windows also matches IE behavior.
      // See bug https://bugs.webkit.org/show_bug.cgi?id=24368
      wheelDeltaScaleFactor = 40;
    }

    detail = goog.events.MouseWheelHandler.smartScale_(
        -be.wheelDelta, wheelDeltaScaleFactor);
    if (goog.isDef(be.wheelDeltaX)) {
      // Webkit has two properties to indicate directional scroll, and
      // can scroll both directions at once.
      deltaX = goog.events.MouseWheelHandler.smartScale_(
          -be.wheelDeltaX, wheelDeltaScaleFactor);
      deltaY = goog.events.MouseWheelHandler.smartScale_(
          -be.wheelDeltaY, wheelDeltaScaleFactor);
    } else {
      deltaY = detail;
    }

    // Historical note: Opera (pre 9.5) used to negate the detail value.
  } else { // Gecko
    // Gecko returns multiple of 3 (representing the number of lines scrolled)
    detail = be.detail;

    // Gecko sometimes returns really big values if the user changes settings to
    // scroll a whole page per scroll
    if (detail > 100) {
      detail = 3;
    } else if (detail < -100) {
      detail = -3;
    }

    // Firefox 3.1 adds an axis field to the event to indicate direction of
    // scroll.  See https://developer.mozilla.org/en/Gecko-Specific_DOM_Events
    if (goog.isDef(be.axis) && be.axis === be.HORIZONTAL_AXIS) {
      deltaX = detail;
    } else {
      deltaY = detail;
    }
  }

  if (goog.isNumber(this.maxDeltaX_)) {
    deltaX = goog.math.clamp(deltaX, -this.maxDeltaX_, this.maxDeltaX_);
  }
  if (goog.isNumber(this.maxDeltaY_)) {
    deltaY = goog.math.clamp(deltaY, -this.maxDeltaY_, this.maxDeltaY_);
  }
  // Don't clamp 'detail', since it could be ambiguous which axis it refers to
  // and because it's informally deprecated anyways.

  // For horizontal scrolling we need to flip the value for RTL grids.
  if (this.isRtl_) {
    deltaX = -deltaX;
  }
  var newEvent = new goog.events.MouseWheelEvent(detail, be, deltaX, deltaY);
  try {
    this.dispatchEvent(newEvent);
  } finally {
    newEvent.dispose();
  }
};

/**
 * Helper for scaling down a mousewheel delta by a scale factor, if appropriate.
 * @param {number} mouseWheelDelta Delta from a mouse wheel event. Expected to
 *     be an integer.
 * @param {number} scaleFactor Factor to scale the delta down by. Expected to
 *     be an integer.
 * @return {number} Scaled-down delta value, or the original delta if the
 *     scaleFactor does not appear to be applicable.
 * @private
 */
goog.events.MouseWheelHandler.smartScale_ = function(mouseWheelDelta,
    scaleFactor) {
  // The basic problem here is that in Webkit on Mac and Linux, we can get two
  // very different types of mousewheel events: from continuous devices
  // (touchpads, Mighty Mouse) or non-continuous devices (normal wheel mice).
  //
  // Non-continuous devices in Webkit get their wheel deltas scaled up to
  // behave like IE. Continuous devices return much smaller unscaled values
  // (which most of the time will not be cleanly divisible by the IE scale
  // factor), so we should not try to normalize them down.
  //
  // Detailed discussion:
  //   https://bugs.webkit.org/show_bug.cgi?id=29601
  //   http://trac.webkit.org/browser/trunk/WebKit/chromium/src/mac/WebInputEventFactory.mm#L1063
  if (goog.userAgent.WEBKIT &&
      (goog.userAgent.MAC || goog.userAgent.LINUX) &&
      (mouseWheelDelta % scaleFactor) != 0) {
    return mouseWheelDelta;
  } else {
    return mouseWheelDelta / scaleFactor;
  }
};

/** @override */
goog.events.MouseWheelHandler.prototype.disposeInternal = function() {
  goog.events.MouseWheelHandler.superClass_.disposeInternal.call(this);
  goog.events.unlistenByKey(this.listenKey_);
  delete this.listenKey_;
};

/**
 * A base class for mouse wheel events. This is used with the
 * MouseWheelHandler.
 *
 * @param {number} detail The number of rows the user scrolled.
 * @param {Event} browserEvent Browser event object.
 * @param {number} deltaX The number of rows the user scrolled in the X
 *     direction.
 * @param {number} deltaY The number of rows the user scrolled in the Y
 *     direction.
 * @constructor
 * @extends {goog.events.BrowserEvent}
 */
goog.events.MouseWheelEvent = function(detail, browserEvent, deltaX, deltaY) {
  goog.events.BrowserEvent.call(this, browserEvent);

  this.type = goog.events.MouseWheelHandler.EventType.MOUSEWHEEL;

  /**
   * The number of lines the user scrolled
   * @type {number}
   * NOTE: Informally deprecated. Use deltaX and deltaY instead, they provide
   * more information.
   */
  this.detail = detail;

  /**
   * The number of "lines" scrolled in the X direction.
   *
   * Note that not all browsers provide enough information to distinguish
   * horizontal and vertical scroll events, so for these unsupported browsers,
   * we will always have a deltaX of 0, even if the user scrolled their mouse
   * wheel or trackpad sideways.
   *
   * Currently supported browsers are Webkit and Firefox 3.1 or later.
   *
   * @type {number}
   */
  this.deltaX = deltaX;

  /**
   * The number of lines scrolled in the Y direction.
   * @type {number}
   */
  this.deltaY = deltaY;
};
goog.inherits(goog.events.MouseWheelEvent, goog.events.BrowserEvent);
<|endoftext|>


<commit_before>var format = require('string-template');

var HELPERS = {
  cache_globally: 'not_found_page_cache_globally',
  set_attribute: 'set_attribute_value',
  add_path: 'add_web_path'
};

exports.keyShorthand = function keyShorthand(key) {
  return HELPERS[key] || key;
};

exports.asset = function asset(type, id) {
  return format('{0}_{1}', type, id);
};

exports.actionId = function actionId(template, expected) {
  return function formatter() {
    var args = Array.prototype.slice.call(arguments);
    expected = expected || 1;
    if (args.length === expected)
      return format(template, args);
    throw new Error(format('Expected {0} arguments instead got {1}', expected, args.length));
  };
};

<commit_msg>Fix error when passing undefined to helpers.actionId<commit_after>var format = require('string-template');

var HELPERS = {
  cache_globally: 'not_found_page_cache_globally',
  set_attribute: 'set_attribute_value',
  add_path: 'add_web_path'
};

exports.keyShorthand = function keyShorthand(key) {
  return HELPERS[key] || key;
};

exports.asset = function asset(type, id) {
  return format('{0}_{1}', type, id);
};

exports.actionId = function actionId(template, expected) {
  return function formatter() {
    var args = Array.prototype.slice.call(arguments);
    expected = expected || 1;
    if (args.length !== expected)
      throw new Error('Expected ' + expected + ' arguments instead got ' + args.length);
    if (args.indexOf(undefined) >= 0)
      throw new Error('Undefined must not be passed to format function');

    return format(template, args);
  };
};

<|endoftext|>


<commit_before>'use strict';

import { REQUEST_TIMEOUT } from '../lib/constants';
import { default as baseRequest } from 'request';
import _ from 'lodash';
import { default as moment } from 'moment-timezone';
import parallelLimit from 'async/parallelLimit';
import Bottleneck from 'bottleneck';
const request = baseRequest.defaults({timeout: REQUEST_TIMEOUT});

// Default rate limiting on API is set to 5 requests/sec.
// > Please send an email to Tools.support@epa.vic.gov.au with subject
// > 'EPA API Access Request: Increase rate-limiting' and a justified
// > reason if you want to get it increased for your subscriptions.
var limiter = new Bottleneck({
  reservoir: 5, // allow 5 requests
  reservoirRefreshAmount: 5,
  reservoirRefreshInterval: 1000, // every 1000ms
  maxConcurrent: 1,
  minTime: 200 + 50 // to stagger requests out throught each second (1000 / 5) adding a 50ms buffer
});

export const name = 'victoria';

export function fetchData (source, cb) {
  limiter.submit(request, {
    url: source.url,
    headers: {
      'X-API-Key': process.env.EPA_VIC_TOKEN
    }
  }, function (err, res, body) {
    if (err || res.statusCode !== 200) {
      return cb({message: 'Failure to load data url.'});
    }

    // Wrap everything in a try/catch in case something goes wrong
    try {
      // Format the data
      formatData(body, function (err, data) {
        // Make sure the data is valid
        if (err || data === undefined) {
          return cb({message: 'Failure to parse data.'});
        }
        cb(null, data);
      });
    } catch (e) {
      return cb({message: 'Unknown adapter error.'});
    }
  });
}

var parameters = {
  'PM2.5': 'pm25',
  'PM10': 'pm10',
  'NO2': 'no2',
  'SO2': 'so2',
  'O3': 'o3',
  'CO': 'co',
  'BC': 'bc'
};

var units = {
  'µg/m³': 'µg/m³',
  'ppm': 'ppm'
};

var formatData = function (data, formatDataCB) {
  var sites = JSON.parse(data).records;

  // request measurements from each site
  var tasks = sites.map(function (site) {
    return function (cb) {
      limiter.submit(request, {
        url: `https://gateway.api.epa.vic.gov.au/environmentMonitoring/v1/sites/${site.siteID}/parameters`,
        headers: {
          'X-API-Key': process.env.EPA_VIC_TOKEN
        }
      }, function (err, res, body) {
        if (err || res.statusCode !== 200) {
          console.error(err, res);
          return cb({message: 'Failure to load data url.'});
        }
        var source = JSON.parse(body);

        // base properties shared for all measurements at this site
        var baseProperties = {
          location: source.siteName,
          city: 'Victoria',
          country: 'AU',
          sourceName: source.name,
          sourceType: 'government',
          attribution: [{
            name: 'EPA Victoria State Government of Victoria',
            url: 'https://www.epa.vic.gov.au/EPAAirWatch'
          }],
          coordinates: {
            latitude: source.geometry.coordinates[0],
            longitude: source.geometry.coordinates[1]
          }
        };

        // list of all measurements at this site
        var measurements = [];
        if (source && source.parameters) {
          measurements = source.parameters.map(function (parameter) {
            if (parameter.name in parameters) {
              var measurement = baseProperties;
              measurement.parameter = parameters[parameter.name];

              // from the range of time series readings, find the 1HR_AV one
              var averageReadings = parameter.timeSeriesReadings.filter(function (timeSeriesReading) {
                return timeSeriesReading.timeSeriesName === '1HR_AV';
              });

              if (averageReadings.length && averageReadings[0].readings.length) {
                var reading = averageReadings[0].readings[0];
                if (reading.unit in units) {
                  measurement.unit = units[reading.unit];
                  measurement.averagingPeriod = { value: 1, unit: 'hours' };
                  measurement.value = Number(reading.averageValue);

                  var date = moment.tz(reading.since, 'Australia/Melbourne');

                  measurement.date = {
                    utc: date.toDate(),
                    local: date.format()
                  };

                  return measurement;
                }
              }
            }
          }).filter(function (measurement) {
            return measurement !== null;
          });
        }

        cb(null, measurements);
      });
    };
  });

  parallelLimit(tasks, 1, function (err, measurements) {
    formatDataCB(err, {name: 'unused', measurements: _.flatten(measurements)});
  });
};
<commit_msg>abstract out magic number into variable<commit_after>'use strict';

import { REQUEST_TIMEOUT } from '../lib/constants';
import { default as baseRequest } from 'request';
import _ from 'lodash';
import { default as moment } from 'moment-timezone';
import parallelLimit from 'async/parallelLimit';
import Bottleneck from 'bottleneck';
const request = baseRequest.defaults({timeout: REQUEST_TIMEOUT});

// Default rate limiting on API is set to 5 requests/sec.
// > Please send an email to Tools.support@epa.vic.gov.au with subject
// > 'EPA API Access Request: Increase rate-limiting' and a justified
// > reason if you want to get it increased for your subscriptions.
var maxRequestsPerSecond = 5;
var limiter = new Bottleneck({
  reservoir: maxRequestsPerSecond, // allow 5 requests
  reservoirRefreshAmount: maxRequestsPerSecond,
  reservoirRefreshInterval: 1000, // every 1000ms
  maxConcurrent: 1,
  minTime: (1000 / maxRequestsPerSecond) + 50 // to stagger requests out throught each second adding a 50ms buffer
});

export const name = 'victoria';

export function fetchData (source, cb) {
  limiter.submit(request, {
    url: source.url,
    headers: {
      'X-API-Key': process.env.EPA_VIC_TOKEN
    }
  }, function (err, res, body) {
    if (err || res.statusCode !== 200) {
      return cb({message: 'Failure to load data url.'});
    }

    // Wrap everything in a try/catch in case something goes wrong
    try {
      // Format the data
      formatData(body, function (err, data) {
        // Make sure the data is valid
        if (err || data === undefined) {
          return cb({message: 'Failure to parse data.'});
        }
        cb(null, data);
      });
    } catch (e) {
      return cb({message: 'Unknown adapter error.'});
    }
  });
}

var parameters = {
  'PM2.5': 'pm25',
  'PM10': 'pm10',
  'NO2': 'no2',
  'SO2': 'so2',
  'O3': 'o3',
  'CO': 'co',
  'BC': 'bc'
};

var units = {
  'µg/m³': 'µg/m³',
  'ppm': 'ppm'
};

var formatData = function (data, formatDataCB) {
  var sites = JSON.parse(data).records;

  // request measurements from each site
  var tasks = sites.map(function (site) {
    return function (cb) {
      limiter.submit(request, {
        url: `https://gateway.api.epa.vic.gov.au/environmentMonitoring/v1/sites/${site.siteID}/parameters`,
        headers: {
          'X-API-Key': process.env.EPA_VIC_TOKEN
        }
      }, function (err, res, body) {
        if (err || res.statusCode !== 200) {
          console.error(err, res);
          return cb({message: 'Failure to load data url.'});
        }
        var source = JSON.parse(body);

        // base properties shared for all measurements at this site
        var baseProperties = {
          location: source.siteName,
          city: 'Victoria',
          country: 'AU',
          sourceName: source.name,
          sourceType: 'government',
          attribution: [{
            name: 'EPA Victoria State Government of Victoria',
            url: 'https://www.epa.vic.gov.au/EPAAirWatch'
          }],
          coordinates: {
            latitude: source.geometry.coordinates[0],
            longitude: source.geometry.coordinates[1]
          }
        };

        // list of all measurements at this site
        var measurements = [];
        if (source && source.parameters) {
          measurements = source.parameters.map(function (parameter) {
            if (parameter.name in parameters) {
              var measurement = baseProperties;
              measurement.parameter = parameters[parameter.name];

              // from the range of time series readings, find the 1HR_AV one
              var averageReadings = parameter.timeSeriesReadings.filter(function (timeSeriesReading) {
                return timeSeriesReading.timeSeriesName === '1HR_AV';
              });

              if (averageReadings.length && averageReadings[0].readings.length) {
                var reading = averageReadings[0].readings[0];
                if (reading.unit in units) {
                  measurement.unit = units[reading.unit];
                  measurement.averagingPeriod = { value: 1, unit: 'hours' };
                  measurement.value = Number(reading.averageValue);

                  var date = moment.tz(reading.since, 'Australia/Melbourne');

                  measurement.date = {
                    utc: date.toDate(),
                    local: date.format()
                  };

                  return measurement;
                }
              }
            }
          }).filter(function (measurement) {
            return measurement !== null;
          });
        }

        cb(null, measurements);
      });
    };
  });

  parallelLimit(tasks, 1, function (err, measurements) {
    formatDataCB(err, {name: 'unused', measurements: _.flatten(measurements)});
  });
};
<|endoftext|>


<commit_before>/* globals AddonManager, Components, LegacyExtensionsUtils, Services,
   XPCOMUtils */

const OLD_ADDON_PREF_NAME = "extensions.jid1-NeEaf3sAHdKHPA@jetpack.deviceIdInfo";
const OLD_ADDON_ID = "jid1-NeEaf3sAHdKHPA@jetpack";
const ADDON_ID = "screenshots@mozilla.org";
const TELEMETRY_ENABLED_PREF = "toolkit.telemetry.enabled";
const PREF_BRANCH = "extensions.screenshots.";
const USER_DISABLE_PREF = "extensions.screenshots.disabled";
const SYSTEM_DISABLE_PREF = "extensions.screenshots.system-disabled";

const { interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
                                  "resource://gre/modules/AddonManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Console",
                                  "resource://gre/modules/Console.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                  "resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LegacyExtensionsUtils",
                                  "resource://gre/modules/LegacyExtensionsUtils.jsm");

let addonResourceURI;
let appStartupDone;
const appStartupPromise = new Promise((resolve, reject) => {
  appStartupDone = resolve;
});

const prefs = Services.prefs;
const prefObserver = {
  register() {
    prefs.addObserver(PREF_BRANCH, this, false); // eslint-disable-line mozilla/no-useless-parameters
  },

  unregister() {
    prefs.removeObserver(PREF_BRANCH, this, false); // eslint-disable-line mozilla/no-useless-parameters
  },

  observe(aSubject, aTopic, aData) {
    // aSubject is the nsIPrefBranch we're observing (after appropriate QI)
    // aData is the name of the pref that's been changed (relative to aSubject)
    if (aData == USER_DISABLE_PREF || aData == SYSTEM_DISABLE_PREF) {
      // eslint-disable-next-line promise/catch-or-return
      appStartupPromise.then(handleStartup);
    }
  }
};

const appStartupObserver = {
  register() {
    Services.obs.addObserver(this, "sessionstore-windows-restored", false); // eslint-disable-line mozilla/no-useless-parameters
  },

  unregister() {
    Services.obs.removeObserver(this, "sessionstore-windows-restored", false); // eslint-disable-line mozilla/no-useless-parameters
  },

  observe() {
    appStartupDone();
    this.unregister();
  }
}

const APP_STARTUP = 1;
function startup(data, reason) { // eslint-disable-line no-unused-vars
  if (reason === APP_STARTUP) {
    appStartupObserver.register();
  } else {
    appStartupDone();
  }
  prefObserver.register();
  addonResourceURI = data.resourceURI;
  // eslint-disable-next-line promise/catch-or-return
  appStartupPromise.then(handleStartup);
}

function shutdown(data, reason) { // eslint-disable-line no-unused-vars
  prefObserver.unregister();
}

function install(data, reason) {} // eslint-disable-line no-unused-vars

function uninstall(data, reason) {} // eslint-disable-line no-unused-vars

function getBoolPref(pref) {
  return prefs.getPrefType(pref) && prefs.getBoolPref(pref);
}

function shouldDisable() {
  return getBoolPref(USER_DISABLE_PREF) || getBoolPref(SYSTEM_DISABLE_PREF);
}

function handleStartup() {
  const webExtension = LegacyExtensionsUtils.getEmbeddedExtensionFor({
    id: ADDON_ID,
    resourceURI: addonResourceURI
  });

  if (!shouldDisable() && !webExtension.started) {
    start(webExtension);
  } else if (shouldDisable()) {
    stop(webExtension);
  }
}

function start(webExtension) {
  webExtension.startup().then((api) => {
    api.browser.runtime.onMessage.addListener(handleMessage);
  }).catch((err) => {
    // The startup() promise will be rejected if the webExtension was
    // already started (a harmless error), or if initializing the
    // WebExtension failed and threw (an important error).
    console.error(err);
    if (err.message !== "This embedded extension has already been started") {
      // TODO: Should we send these errors to Sentry? #2420
    }
  });
}

function stop(webExtension) {
  webExtension.shutdown();
}

function handleMessage(msg, sender, sendReply) {
  if (!msg) {
    return;
  }

  if (msg.funcName === "getTelemetryPref") {
    let telemetryEnabled = getBoolPref(TELEMETRY_ENABLED_PREF);
    sendReply({type: "success", value: telemetryEnabled});
  } else if (msg.funcName === "getOldDeviceInfo") {
    let oldDeviceInfo = prefs.prefHasUserValue(OLD_ADDON_PREF_NAME) && prefs.getCharPref(OLD_ADDON_PREF_NAME);
    sendReply({type: "success", value: oldDeviceInfo || null});
  } else if (msg.funcName === "removeOldAddon") {
    AddonManager.getAddonByID(OLD_ADDON_ID, (addon) => {
      prefs.clearUserPref(OLD_ADDON_PREF_NAME);
      if (addon) {
        addon.uninstall();
      }
      sendReply({type: "success", value: !!addon});
    });
    return true;
  }
}
<commit_msg>Shutdown the embedded webExtension when bootstrap is asked to shutdown (#2712)<commit_after>/* globals AddonManager, Components, LegacyExtensionsUtils, Services,
   XPCOMUtils */

const OLD_ADDON_PREF_NAME = "extensions.jid1-NeEaf3sAHdKHPA@jetpack.deviceIdInfo";
const OLD_ADDON_ID = "jid1-NeEaf3sAHdKHPA@jetpack";
const ADDON_ID = "screenshots@mozilla.org";
const TELEMETRY_ENABLED_PREF = "toolkit.telemetry.enabled";
const PREF_BRANCH = "extensions.screenshots.";
const USER_DISABLE_PREF = "extensions.screenshots.disabled";
const SYSTEM_DISABLE_PREF = "extensions.screenshots.system-disabled";

const { interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
                                  "resource://gre/modules/AddonManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Console",
                                  "resource://gre/modules/Console.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                  "resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LegacyExtensionsUtils",
                                  "resource://gre/modules/LegacyExtensionsUtils.jsm");

let addonResourceURI;
let appStartupDone;
const appStartupPromise = new Promise((resolve, reject) => {
  appStartupDone = resolve;
});

const prefs = Services.prefs;
const prefObserver = {
  register() {
    prefs.addObserver(PREF_BRANCH, this, false); // eslint-disable-line mozilla/no-useless-parameters
  },

  unregister() {
    prefs.removeObserver(PREF_BRANCH, this, false); // eslint-disable-line mozilla/no-useless-parameters
  },

  observe(aSubject, aTopic, aData) {
    // aSubject is the nsIPrefBranch we're observing (after appropriate QI)
    // aData is the name of the pref that's been changed (relative to aSubject)
    if (aData == USER_DISABLE_PREF || aData == SYSTEM_DISABLE_PREF) {
      // eslint-disable-next-line promise/catch-or-return
      appStartupPromise.then(handleStartup);
    }
  }
};

const appStartupObserver = {
  register() {
    Services.obs.addObserver(this, "sessionstore-windows-restored", false); // eslint-disable-line mozilla/no-useless-parameters
  },

  unregister() {
    Services.obs.removeObserver(this, "sessionstore-windows-restored", false); // eslint-disable-line mozilla/no-useless-parameters
  },

  observe() {
    appStartupDone();
    this.unregister();
  }
}

const APP_STARTUP = 1;
function startup(data, reason) { // eslint-disable-line no-unused-vars
  if (reason === APP_STARTUP) {
    appStartupObserver.register();
  } else {
    appStartupDone();
  }
  prefObserver.register();
  addonResourceURI = data.resourceURI;
  // eslint-disable-next-line promise/catch-or-return
  appStartupPromise.then(handleStartup);
}

function shutdown(data, reason) { // eslint-disable-line no-unused-vars
  prefObserver.unregister();
  const webExtension = LegacyExtensionsUtils.getEmbeddedExtensionFor({
    id: ADDON_ID,
    resourceURI: addonResourceURI
  });
  if (webExtension.started) {
    stop(webExtension);
  }
}

function install(data, reason) {} // eslint-disable-line no-unused-vars

function uninstall(data, reason) {} // eslint-disable-line no-unused-vars

function getBoolPref(pref) {
  return prefs.getPrefType(pref) && prefs.getBoolPref(pref);
}

function shouldDisable() {
  return getBoolPref(USER_DISABLE_PREF) || getBoolPref(SYSTEM_DISABLE_PREF);
}

function handleStartup() {
  const webExtension = LegacyExtensionsUtils.getEmbeddedExtensionFor({
    id: ADDON_ID,
    resourceURI: addonResourceURI
  });

  if (!shouldDisable() && !webExtension.started) {
    start(webExtension);
  } else if (shouldDisable()) {
    stop(webExtension);
  }
}

function start(webExtension) {
  webExtension.startup().then((api) => {
    api.browser.runtime.onMessage.addListener(handleMessage);
  }).catch((err) => {
    // The startup() promise will be rejected if the webExtension was
    // already started (a harmless error), or if initializing the
    // WebExtension failed and threw (an important error).
    console.error(err);
    if (err.message !== "This embedded extension has already been started") {
      // TODO: Should we send these errors to Sentry? #2420
    }
  });
}

function stop(webExtension) {
  webExtension.shutdown();
}

function handleMessage(msg, sender, sendReply) {
  if (!msg) {
    return;
  }

  if (msg.funcName === "getTelemetryPref") {
    let telemetryEnabled = getBoolPref(TELEMETRY_ENABLED_PREF);
    sendReply({type: "success", value: telemetryEnabled});
  } else if (msg.funcName === "getOldDeviceInfo") {
    let oldDeviceInfo = prefs.prefHasUserValue(OLD_ADDON_PREF_NAME) && prefs.getCharPref(OLD_ADDON_PREF_NAME);
    sendReply({type: "success", value: oldDeviceInfo || null});
  } else if (msg.funcName === "removeOldAddon") {
    AddonManager.getAddonByID(OLD_ADDON_ID, (addon) => {
      prefs.clearUserPref(OLD_ADDON_PREF_NAME);
      if (addon) {
        addon.uninstall();
      }
      sendReply({type: "success", value: !!addon});
    });
    return true;
  }
}
<|endoftext|>