<commit_before>/**
 * Copyright reelyActive 2015
 * We believe in an open Internet of Things
 */

var http = require('http');
var express = require('express');

var HTTP_PORT = 3001;

/**
 * Barterer Class
 * API for real-time hyperlocal context.
 * @param {Object} options The options as a JSON object.
 * @constructor
 */
function Barterer(options) {
  var self = this;
  options = options || {};
  this.specifiedHttpPort = options.httpPort || HTTP_PORT;
  this.httpPort = process.env.PORT || this.specifiedHttpPort;

  this.app = express();

  this.app.get('/id/:id', function(req, res) {
    searchByIDs(self, getRequestParameters(req), function(result) {
      res.json(result);
    });
  });

  console.log("reelyActive Barterer instance is exchanging data in an open IoT");

  this.app.listen(this.httpPort, function() {
    console.log("barterer is listening on port", self.httpPort);
  });
};

/**
 * Bind the API to the given data source.
 * @param {Object} options The options as a JSON object.
 */
Barterer.prototype.bind = function(options) {
  options = options || {};
  var self = this;

  if(options.barnacles) {
    self.dataStore = options.barnacles;
  }
}

/**
 * Return the API request parameters as an object.
 * @param {Object} req The request.
 */
function getRequestParameters(req) {
  var params = {};
  params.ids = [ req.param('id') ];
  params.place = req.param('place');
  params.rootUrl = req.protocol + '://' + req.get('host');
  params.queryPath = req.originalUrl;
  params.body = req.body;
  return params;
}

/**
 * Search hyperlocal context based on identifiers.
 * @param {Barterer} instance The given Barterer instance.
 * @param {Object} params The parameters to search on.
 * @param {function} callback Function to call on completion.
 */
function searchByIDs(instance, params, callback) {
  instance.dataStore.getState( { ids: params.ids }, function(state) {
    callback(state);
  });
}

module.exports = Barterer;
<commit_msg>Added response handler<commit_after>/**
 * Copyright reelyActive 2015
 * We believe in an open Internet of Things
 */

var http = require('http');
var express = require('express');
var responseHandler = require('./responsehandler');

var HTTP_PORT = 3001;

/**
 * Barterer Class
 * API for real-time hyperlocal context.
 * @param {Object} options The options as a JSON object.
 * @constructor
 */
function Barterer(options) {
  var self = this;
  options = options || {};
  this.specifiedHttpPort = options.httpPort || HTTP_PORT;
  this.httpPort = process.env.PORT || this.specifiedHttpPort;

  this.app = express();

  this.app.get('/id/:id', function(req, res) {
    searchByIDs(self, getRequestParameters(req), function(result) {
      res.json(result);
    });
  });

  console.log("reelyActive Barterer instance is exchanging data in an open IoT");

  this.app.listen(this.httpPort, function() {
    console.log("barterer is listening on port", self.httpPort);
  });
};

/**
 * Bind the API to the given data source.
 * @param {Object} options The options as a JSON object.
 */
Barterer.prototype.bind = function(options) {
  options = options || {};
  var self = this;

  if(options.barnacles) {
    self.dataStore = options.barnacles;
  }
}

/**
 * Return the API request parameters as an object.
 * @param {Object} req The request.
 */
function getRequestParameters(req) {
  var params = {};
  params.ids = [ req.param('id') ];
  params.rootUrl = req.protocol + '://' + req.get('host');
  params.queryPath = req.originalUrl;
  params.body = req.body;
  return params;
}

/**
 * Search based on device identifiers.
 * @param {Barterer} instance The given Barterer instance.
 * @param {Object} params The parameters to search on.
 * @param {function} callback Function to call on completion.
 */
function searchByIDs(instance, params, callback) {
  instance.dataStore.getState( { ids: params.ids }, function(state) {
    var foundNothing = !Object.keys(state).length;
    if(foundNothing) {
      callback(responseHandler.prepareFailureResponse("notFound"));
    }
    else {
      callback(responseHandler.prepareResponse(state, params));
    }
  });
}

module.exports = Barterer;
<|endoftext|>


<commit_before>const path = require('path')
const http = require('http')
const connect = require('connect')
const WebSocket = require('ws')
const Util = require('./util')
const ChromeWrapper = require('./chrome')

module.exports = class Server {
  constructor () {
    this.runId = 1
    this.head = null
    this.workers = Array.construct(8, id => ({id: id + 1}))
    this.isLambda = false
    this.workerCount = 0
    this.workingSet = []
    this.results = [] // results of all tests run
    this.passedFocus = [] // all tests that passed after running

    // start up the local webserver for `head` to connect to
    let app = connect()
    let server = http.createServer(app).listen(Zen.config.port)
    app.use('/lib', Util.serveWith404(__dirname)) // serve up stuff out of lib
    app.use('/node_modules', Util.serveWith404(path.resolve(Zen.config.appRoot, './node_modules'))) // serve up stuff out of lib
    app.use('/base', Util.serveWith404(Zen.config.appRoot)) // base serves things out of the application's root
    app.use('/svelte', Util.serveSvelte)
    app.use('/icons', Util.serveIcons)

    if (Zen.webpack) {
      Zen.webpack.startDevServer(app)
      Zen.webpack.on('status', stats => {
        if (Zen.webpack.status == 'done') {
          this.workers.forEach(w => w.tab && w.tab.setCodeHash(Zen.webpack.compile.hash))
        }
        this.sendStatus() // notify head of the compile status
      })
    }

    Zen.s3Sync.on('status', this.sendStatus.bind(this))

    this.chrome = new ChromeWrapper() // headless chrome instance
    this.chrome.launchLocal({port: 9222})
    new WebSocket.Server({server}).on('connection', this.onWebsocket.bind(this))

    // create a server for each worker. This gives us different origins and isolates things like localStorage
    this.workers = Array.construct(8, id => {
      id = id + 1
      let port = Zen.config.port + id
      http.createServer(app).listen(port)
      let worker = {id, port}
      this.chrome.openTab(`http://localhost:${port}/worker?id=${id}`, `w${id}`, Zen.config)
        .then(t => worker.tab = t)
      return worker
    })

    // host worker and head. NB this should go last after all other `app.use()` calls
    app.use(async (req, resp) => {
      resp.end(Zen.indexHtml(req.url.match(/^\/worker/) ? 'worker' : 'head'))
    })
  }

  filterTests (msg) {
    // If nothing has changed and we're not running, leave the state unchanged.
    // When you refresh `head`, we don't want to run or clear out results unless the grep changed.
    if (!msg.run && this.grep === msg.grep) return

    if (msg.failed) {
      this.workingSet = this.results.filter(r => r.error).map(r => r.fullName)
    } else {
      this.workingSet = msg.testNames
    }

    this.grep = msg.grep
    this.results = []
    this.passedFocus = []

    if (msg.run && Zen.webpack.status == 'done') {
      if (msg.reload)
        this.workers.forEach(w => w.tab.reload())
      this.runId++
      this.grep ? this.runLocally() : this.runOnLambda() // should this be if the tests will take less than x seconds?
      this.sendStatus()
    }
  }

  async runOnLambda () {
    let startingRunId = this.runId
    let runGroups = Zen.journal.groupTests(this.workingSet, Zen.config.lambdaConcurrency)
    this.isLambda = true
    this.workerCount = runGroups.length

    // send manifest to proxy
    await Zen.s3Sync.run(Zen.indexHtml('worker', true))
    this.sendStatus()

    runGroups.forEach(async (group) => {
      try {
        let response = await Util.invoke('zen-workTests', {testNames: group.tests, sessionId: Zen.config.sessionId})
        if (startingRunId !== this.runId) return
        this.onResults(response)
      } catch (e) {
        if (startingRunId !== this.runId) return
        this.onResults(testNames.map((fullName, testNumber) => {
          return {error: e.message, fullName, batchId: workerId, testNumber}
        }))
      }
    })
  }

  async runLocally() {
    let startingRunId = this.runId, remaining = this.workingSet.clone()
    this.isLambda = false
    this.workerCount = this.workers.length

    this.workers.forEach(async w => {
      while (remaining.length > 0) {
        let result = await w.tab.setTest({testName: remaining.pop()})
        if (!result || startingRunId !== this.runId) break // if the run was aborted
        this.onResults([result])
      }
    })
  }

  onWebsocket (ws) {
    this.head = ws
    ws.on('message', msg => {
      msg = JSON.parse(msg)
      if (msg.type === 'filterTests') this.filterTests(msg)
      if (msg.type === 'passedFocus') this.passedFocus.push(msg.test)
      this.sendStatus()
    })
    ws.on('error', err => {
      console.error('Websocket error', err)
      this.head = null
    })
    this.sendStatus()
  }

  onResults (step) {
    this.results.push.apply(this.results, step)
    step.forEach(r => Zen.journal.record(r))
    Util.wsSend(this.head, {results: step})
  }

  sendStatus () {
    Util.wsSend(this.head, Object.assign(
      Object.select(this, 'runId results isLambda workerCount passedFocus'.split(' ')), {
        workingSetLength: this.workingSet.length,
        s3: Zen.s3Sync.status,
        compile: Object.select(Zen.webpack.compile, ['hash', 'status', 'errors', 'percentage', 'message']) // exclude files array, which has contains content
      }
    ))
  }
}
<commit_msg>Add note<commit_after>const path = require('path')
const http = require('http')
const connect = require('connect')
const WebSocket = require('ws')
const Util = require('./util')
const ChromeWrapper = require('./chrome')

module.exports = class Server {
  constructor () {
    this.runId = 1
    this.head = null
    this.workers = Array.construct(8, id => ({id: id + 1}))
    this.isLambda = false
    this.workerCount = 0
    this.workingSet = []
    this.results = [] // results of all tests run
    this.passedFocus = [] // all tests that passed after running

    // start up the local webserver for `head` to connect to
    let app = connect()
    let server = http.createServer(app).listen(Zen.config.port)
    app.use('/lib', Util.serveWith404(__dirname)) // serve up stuff out of lib
    app.use('/node_modules', Util.serveWith404(path.resolve(Zen.config.appRoot, './node_modules'))) // TODO: this doesn't work when yarn link'd
    app.use('/base', Util.serveWith404(Zen.config.appRoot)) // base serves things out of the application's root
    app.use('/svelte', Util.serveSvelte)
    app.use('/icons', Util.serveIcons)

    if (Zen.webpack) {
      Zen.webpack.startDevServer(app)
      Zen.webpack.on('status', stats => {
        if (Zen.webpack.status == 'done') {
          this.workers.forEach(w => w.tab && w.tab.setCodeHash(Zen.webpack.compile.hash))
        }
        this.sendStatus() // notify head of the compile status
      })
    }

    Zen.s3Sync.on('status', this.sendStatus.bind(this))

    this.chrome = new ChromeWrapper() // headless chrome instance
    this.chrome.launchLocal({port: 9222})
    new WebSocket.Server({server}).on('connection', this.onWebsocket.bind(this))

    // create a server for each worker. This gives us different origins and isolates things like localStorage
    this.workers = Array.construct(8, id => {
      id = id + 1
      let port = Zen.config.port + id
      http.createServer(app).listen(port)
      let worker = {id, port}
      this.chrome.openTab(`http://localhost:${port}/worker?id=${id}`, `w${id}`, Zen.config)
        .then(t => worker.tab = t)
      return worker
    })

    // host worker and head. NB this should go last after all other `app.use()` calls
    app.use(async (req, resp) => {
      resp.end(Zen.indexHtml(req.url.match(/^\/worker/) ? 'worker' : 'head'))
    })
  }

  filterTests (msg) {
    // If nothing has changed and we're not running, leave the state unchanged.
    // When you refresh `head`, we don't want to run or clear out results unless the grep changed.
    if (!msg.run && this.grep === msg.grep) return

    if (msg.failed) {
      this.workingSet = this.results.filter(r => r.error).map(r => r.fullName)
    } else {
      this.workingSet = msg.testNames
    }

    this.grep = msg.grep
    this.results = []
    this.passedFocus = []

    if (msg.run && Zen.webpack.status == 'done') {
      if (msg.reload)
        this.workers.forEach(w => w.tab.reload())
      this.runId++
      this.grep ? this.runLocally() : this.runOnLambda() // should this be if the tests will take less than x seconds?
      this.sendStatus()
    }
  }

  async runOnLambda () {
    let startingRunId = this.runId
    let runGroups = Zen.journal.groupTests(this.workingSet, Zen.config.lambdaConcurrency)
    this.isLambda = true
    this.workerCount = runGroups.length

    // send manifest to proxy
    await Zen.s3Sync.run(Zen.indexHtml('worker', true))
    this.sendStatus()

    runGroups.forEach(async (group) => {
      try {
        let response = await Util.invoke('zen-workTests', {testNames: group.tests, sessionId: Zen.config.sessionId})
        if (startingRunId !== this.runId) return
        this.onResults(response)
      } catch (e) {
        if (startingRunId !== this.runId) return
        this.onResults(testNames.map((fullName, testNumber) => {
          return {error: e.message, fullName, batchId: workerId, testNumber}
        }))
      }
    })
  }

  async runLocally() {
    let startingRunId = this.runId, remaining = this.workingSet.clone()
    this.isLambda = false
    this.workerCount = this.workers.length

    this.workers.forEach(async w => {
      while (remaining.length > 0) {
        let result = await w.tab.setTest({testName: remaining.pop()})
        if (!result || startingRunId !== this.runId) break // if the run was aborted
        this.onResults([result])
      }
    })
  }

  onWebsocket (ws) {
    this.head = ws
    ws.on('message', msg => {
      msg = JSON.parse(msg)
      if (msg.type === 'filterTests') this.filterTests(msg)
      if (msg.type === 'passedFocus') this.passedFocus.push(msg.test)
      this.sendStatus()
    })
    ws.on('error', err => {
      console.error('Websocket error', err)
      this.head = null
    })
    this.sendStatus()
  }

  onResults (step) {
    this.results.push.apply(this.results, step)
    step.forEach(r => Zen.journal.record(r))
    Util.wsSend(this.head, {results: step})
  }

  sendStatus () {
    Util.wsSend(this.head, Object.assign(
      Object.select(this, 'runId results isLambda workerCount passedFocus'.split(' ')), {
        workingSetLength: this.workingSet.length,
        s3: Zen.s3Sync.status,
        compile: Object.select(Zen.webpack.compile, ['hash', 'status', 'errors', 'percentage', 'message']) // exclude files array, which has contains content
      }
    ))
  }
}
<|endoftext|>


<commit_before>'use strict';

const AuthServer = require('oauth2-server');
const Request = require('oauth2-server').Request;
const Response = require('oauth2-server').Response;
const InvalidArgumentError = require('oauth2-server/lib/errors/invalid-argument-error');
const UnauthorizedRequestError = require('oauth2-server/lib/errors/unauthorized-request-error');

const SERVER = Symbol('server#oauth2');

/**
 * replace response.
 */

function replaceResponse(res) {
  // copy for response.headers.hasOwnProperty is undefined case
  // https://github.com/oauthjs/node-oauth2-server/pull/486
  const newResponse = {
    headers: {},
  };
  for (const property in res) {
    if (property !== 'headers') {
      newResponse[property] = res[property];
    }
  }
  for (const field in res.headers) {
    newResponse.headers[field] = res.headers[field];
  }
  newResponse.header = newResponse.headers;
  return newResponse;
}

/**
 * Handle response.
 */

function handleResponse(ctx, response) {
  ctx.body = response.body;
  ctx.status = response.status;
  ctx.set(response.headers);
}

/**
 * Handle error.
 */

function handleError(ctx, e, response) {
  if (response) {
    ctx.set(response.headers);
  }

  if (e instanceof UnauthorizedRequestError) {
    ctx.status = e.code;
  } else {
    ctx.body = { error: e.name, error_description: e.message };
    ctx.status = e.code;
  }
  return ctx.app.emit('error', e, ctx);
}

class OAuth2 {
  constructor(config, model) {
    if (!model) {
      throw new InvalidArgumentError('Missing parameter: `model`');
    }
    this.config = config;
    this.model = model;
  }

  get server() {
    if (!this[SERVER]) {
      const { config, model: Model, ctx } = this;
      const model = new Model(ctx);
      this[SERVER] = new AuthServer(Object.assign(config, { model }));
      return this[SERVER];
    }
    return this[SERVER];
  }

  async execute(handle, ctx, options) {
    let result = null;
    this.ctx = ctx;
    const request = new Request(ctx.request);
    const response = new Response(replaceResponse(ctx.response));
    try {
      result = await this.server[handle](request, response, options);
      handleResponse(ctx, response);
    } catch (e) {
      handleError(ctx, e, response);
    }
    return result;
  }

  token(options) {
    return async (ctx, next) => {
      const token = await this.execute('token', ctx, options);
      ctx.state.oauth = {
        token,
      };
      if (token) {
        await next();
      }
    };
  }

  authenticate(options) {
    return async (ctx, next) => {
      const token = await this.execute('authenticate', ctx, options);
      ctx.state.oauth = {
        token,
      };
      if (token) {
        await next();
      }
    };
  }

  authorize(options) {
    options = options || {};
    const self = this;
    return async (ctx, next) => {
      const opts = Object.assign(options, {
        authenticateHandler: {
          async handle(req) {
            const { username, password } = req.body;
            const user = await self.server.options.model.getUser(
              username,
              password
            );
            return user;
          },
        },
      });
      const code = await this.execute('authorize', ctx, opts);
      ctx.state.oauth = {
        code,
      };
      if (code) {
        await next();
      }
    };
  }
}

module.exports = OAuth2;
<commit_msg>Don't expose server error messages (#40)<commit_after>'use strict';

const AuthServer = require('oauth2-server');
const Request = require('oauth2-server').Request;
const Response = require('oauth2-server').Response;
const InvalidArgumentError = require('oauth2-server/lib/errors/invalid-argument-error');
const UnauthorizedRequestError = require('oauth2-server/lib/errors/unauthorized-request-error');
const ServerError = require('oauth2-server/lib/errors/server-error');

const SERVER = Symbol('server#oauth2');

/**
 * replace response.
 */

function replaceResponse(res) {
  // copy for response.headers.hasOwnProperty is undefined case
  // https://github.com/oauthjs/node-oauth2-server/pull/486
  const newResponse = {
    headers: {},
  };
  for (const property in res) {
    if (property !== 'headers') {
      newResponse[property] = res[property];
    }
  }
  for (const field in res.headers) {
    newResponse.headers[field] = res.headers[field];
  }
  newResponse.header = newResponse.headers;
  return newResponse;
}

/**
 * Handle response.
 */

function handleResponse(ctx, response) {
  ctx.body = response.body;
  ctx.status = response.status;
  ctx.set(response.headers);
}

/**
 * Handle error.
 */

function handleError(ctx, e, response) {
  if (response) {
    ctx.set(response.headers);
  }

  if (e instanceof UnauthorizedRequestError) {
    ctx.status = e.code;
  } else {
    ctx.body = {
      error: e.name,
      error_description: (e instanceof ServerError && ctx.app.config.env === 'prod') ? 'Server Error' : e.message
    };
    ctx.status = e.code;
  }
  return ctx.app.emit('error', e, ctx);
}

class OAuth2 {
  constructor(config, model) {
    if (!model) {
      throw new InvalidArgumentError('Missing parameter: `model`');
    }
    this.config = config;
    this.model = model;
  }

  get server() {
    if (!this[SERVER]) {
      const { config, model: Model, ctx } = this;
      const model = new Model(ctx);
      this[SERVER] = new AuthServer(Object.assign(config, { model }));
      return this[SERVER];
    }
    return this[SERVER];
  }

  async execute(handle, ctx, options) {
    let result = null;
    this.ctx = ctx;
    const request = new Request(ctx.request);
    const response = new Response(replaceResponse(ctx.response));
    try {
      result = await this.server[handle](request, response, options);
      handleResponse(ctx, response);
    } catch (e) {
      handleError(ctx, e, response);
    }
    return result;
  }

  token(options) {
    return async (ctx, next) => {
      const token = await this.execute('token', ctx, options);
      ctx.state.oauth = {
        token,
      };
      if (token) {
        await next();
      }
    };
  }

  authenticate(options) {
    return async (ctx, next) => {
      const token = await this.execute('authenticate', ctx, options);
      ctx.state.oauth = {
        token,
      };
      if (token) {
        await next();
      }
    };
  }

  authorize(options) {
    options = options || {};
    const self = this;
    return async (ctx, next) => {
      const opts = Object.assign(options, {
        authenticateHandler: {
          async handle(req) {
            const { username, password } = req.body;
            const user = await self.server.options.model.getUser(
              username,
              password
            );
            return user;
          },
        },
      });
      const code = await this.execute('authorize', ctx, opts);
      ctx.state.oauth = {
        code,
      };
      if (code) {
        await next();
      }
    };
  }
}

module.exports = OAuth2;
<|endoftext|>


<commit_before>/*
 * This implements a OAuth2 server methods, as specified at:
 *  http://tools.ietf.org/html/draft-ietf-oauth-v2-10
 *
 * Only features the "web server" schema:
 *  http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-1.4.1
 *
 * Terminology:
 *  http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-1.2
 *
 */
var url = require('url')
  , querystring = require('querystring')

  , serializer = require('serializer')
  , randomString = serializer.randomString
  , router = require('connect').router

  , oauth2 = require('./common')
  ;

function redirect(res, url) {
  res.writeHead(303, {'Location': url});
  res.end();
}

function server_error(res, err) {
  res.writeHead(500, {'Content-Type': 'text/plain'});
  if(typeof err == "string") res.end(err);
  else {
    res.write('An error has occured: ' + err.message);
    res.write('\n\n');
    res.end(err.stack);
  }
}

var SERVER = exports;
// To be set by connect middleware: SERVER.RFactory, SERVER.authentication

SERVER.oauth_error = function(res, type, id) {
  /* Render a particular error.
   *
   * Arguments:
   *  - res
   *  - type: the class of the error ('eua' or 'oat').
   *  - id: the id of the error (invalid_request, invalid_client...).
   */
  res.writeHead(400, {'Content-Type': 'text/plain'});
  res.end(JSON.stringify({error: {
    type: 'OAuthException',
    message: id + ': ' + oauth2.ERRORS[type][id],
  }}));
};

var unknown_error = exports.unknown_error = function(res, err) {
  /* To call when an unknown error happens (server error).
   */
  server_error(res, err);
};

// Parameters we must/can have in different kinds of requests:
var PARAMS = exports.PARAMS = {
  eua: { // eua = end user authorization
    mandatory: ['client_id', 'response_type'],
    optional: ['state', 'scope', 'redirect_uri'],
    // possible values for response_type param:
    response_types: {'token': 1, 'code': 1, 'code_and_token': 1},
  },

  oat: { // oat = Obtaining an access token
    mandatory: ['grant_type', 'client_id', 'code', 'redirect_uri'],
    // client_secret might be provided with the 'Authorization: Basic ...' header
    optional: ['scope', 'client_secret'],
  },
};
PARAMS.eua.all = PARAMS.eua.mandatory.concat(PARAMS.eua.optional);

exports.send_grant = function(res, Grant, user_id, client_data, additional_info) {
  /* Create a grant and send it to the user.
   * The code sent is of the form: grand.id + '.' + grant.code
   *
   * Arguments:
   *  - req
   *  - res
   *  - Grant:
   *  - user_id: id of the user
   *  - client_data: hash, data about the client:
   *      - client_id
   *      - redirect_uri
   *      - state: str, optional, if given, will be sent with the grant.
   *  - additional_info: optional hash, anything you want to be stored with
   *    the grant.
   *
   */
  var grant = new Grant({
    client_id: client_data.client_id,
    time: Date.now(),
    user_id: user_id,
    code: randomString(128),
    redirect_uri: client_data.redirect_uri
  });
  if(additional_info) grant.additional_info = additional_info;
    grant.save(function(err, result) {
    var qs = {code: grant.id + '.' + grant.code};
    if(client_data.state) qs.state = client_data.state;
    qs = querystring.stringify(qs);
    redirect(res, client_data.redirect_uri + '?' + qs);
  });
};

/**
 * Valid the grant, call callback(err, token|null)
 * token being a JSON object.
 * If valid, the grant is invalidated and cannot be used anymore.
 *
 * To be valid, a grant must exist, not be deprecated and have the right
 * associated client.
 *
 * Arguments:
 *  - Grant: Grant instance
 *  - data:
 *   - code: grant code given by client.
 *   - client_id: the client id giving the grant
 *   - redirect_uri: the redirect_uri given with the grant
 *  - callback: to be called with a token if the grant was valid,
 *    or null otherwise.
 *
 */
SERVER.valid_grant = function(Grant, data, callback) {
  var id_code = data.code.split('.');
  if(id_code.length != 2) return callback(null);
  Grant.getById(id_code[0], function(err, grant) {
    if (err) return callback(err);
    var minute_ago = Date.now() - 60000;
    if(!grant || grant.time < minute_ago ||
       grant.client_id != data.client_id ||
       grant.code != id_code[1] ||
       grant.redirect_uri != data.redirect_uri
       ) return callback('error in grant');
    var additional_info = grant.additional_info;
    // Delete the grant so that it cannot be used anymore:
    grant.del(function(err, result) {
      if (err) return callback(err);
      // Generate and send an access_token to the client:
      var token = {
        access_token: oauth2.create_access_token(grant.user_id, grant.client_id,
                                                 additional_info)
        // optional: expires_in, refresh_token, scope
      };
      callback(null, token);
    });
  });
};

/**
 * OAuth2 token endpoint.
 * Check the authorization_code, uri_redirect and client secret, issue a token.
 *
 * POST to config.oauth2.token_url
 *
 * Arguments:
 *  - req
 *  - res
 *
 */
var token_endpoint = exports.token_endpoint = function(req, res) {
  if(!req.form) return SERVER.oauth_error(res, 'oat', 'invalid_request');
  req.form.complete(function(err, params, files) {
    if(err) return SERVER.oauth_error(res, 'oat', 'invalid_request');

    // We check there is no invalid_requet error:
    var error = false;
    params && PARAMS.oat.mandatory.forEach(function(param) {
      if(!params[param]) error = true;
    });
    if(error) return SERVER.oauth_error(res, 'oat', 'invalid_request');

    // We do only support 'authorization_code' as grant_type:
    if(params.grant_type != 'authorization_code')
      return SERVER.oauth_error(res, 'oat', 'unsupported_grant_type');

    // Check the client_secret is given once (and only once),
    // either by HTTP basic auth, or by client_secret parameter:
    var secret = req.headers['authorization'];
    if(secret) {
      if(params.client_secret)
        return SERVER.oauth_error(res, 'oat', 'invalid_request');
      params.client_secret = secret.slice(6); // remove the leading 'Basic'
    }
    else if(!params.client_secret) {
      return SERVER.oauth_error(res, 'oat', 'invalid_request');
    }
    var model = SERVER.Model
      , Client = model.Client
      , Grant = model.Grant;

    // Check the client_id exists and does have correct client_secret:
    Client.getById(params.client_id, function(err, client) {
      if (err) return unknown_error(res, err);
      if(!client || client.secret != params.client_secret)
        return SERVER.oauth_error(res, 'oat', 'invalid_client');

      var data = {code: params.code, client_id: client.id, redirect_uri: params.redirect_uri};
      SERVER.valid_grant(Grant, data, function(err, token) {
        if (err) return unknown_error(res, err);
        if(!token) return SERVER.oauth_error(res, 'oat', 'invalid_grant');
        res.writeHead(200, { 'Content-Type': 'application/json'
                           , 'Cache-Control': 'no-store'
                           });
        res.end(JSON.stringify(token));
      });
    });
  });
};

SERVER.authorize = function(params, req, res) {
  /* OAuth2 Authorize function.
   * Serve an authentication form to the end user at browser.
   *
   *  This function should only be called by oauth2.authorize
   *
   * Arguments:
   *  - params:
   *  - req
   *  - res
   *
   */
  // We check there is no invalid_requet error:
  var error = false;
  PARAMS.eua.mandatory.forEach(function(param) {
    if(!params[param]) error = true;
  });
  if(error) return SERVER.oauth_error(res, 'eua', 'invalid_request');
  if(!PARAMS.eua.response_types[params.response_type])
    return SERVER.oauth_error(res, 'eua', 'unsupported_response_type');

  // XXX: For now, we only support 'code' response type
  // which is used in case of a web server (Section 1.4.1 in oauth2 spec draft 10)
  // TODO: make it more compliant with the specs
  if(params.response_type != "code") {
    res.writeHead(501, {'Content-Type': 'text/plain'});
    res.end('Only code request type supported for now ' +
            '(schema 1.4.1 in oauth2 spec draft 10).');
    return;
  }

  var Client = SERVER.Model.Client;
 Client.getById(params.client_id, function(err, client) {
    if (err)  return unknown_error(res, err);
    if(!client) return SERVER.oauth_error(res, 'eua', 'invalid_client');
    // Check the redirect_uri is the one we know about (if we do):
    if(client.redirect_uri && params.redirect_uri
       && client.redirect_uri != params.redirect_uri) {
      return SERVER.oauth_error(res, 'eua', 'redirect_uri_mismatch');
    }
    var redirect_uri = client.redirect_uri || params.redirect_uri;
    // Eveything is allright, ask the user to sign in.
    SERVER.authentication.login(req, res, {
      client_id: client.id,
      client_name: client.name,
      redirect_uri: redirect_uri,
      state: params.state
    });
  });
};

SERVER.authorize_endpoint = function(req, res) {
  /* OAuth2 Authorize end-point.
   * Serve an authentication form to the end user at browser.
   *
   *  GET or POST on config.oauth2.authorize_url
   *
   * Arguments:
   *  - req
   *  - res
   *
   */
  if (req.method == 'GET') {
    var params = url.parse(req.url, true).query;
    return SERVER.authorize(params, req, res);
  } else {
    if(!req.form) return SERVER.oauth_error(res, 'eua', 'invalid_request');
    req.form.complete(function(err, fields, files) {
      if(err) return SERVER.oauth_error(res, 'eua', 'invalid_request');
      SERVER.authorize(fields, req, res);
    });
  }
};

exports.connector = function(config, Model, authentication) {
  /* Returns Oauth2 server connect middleware.
   *
   * This middleware will intercept requests aiming at OAuth2 server
   * and treat them.
   *
   * Arguments:
   *  - config, hash containing:
   *    - authorize_url: end-user authorization endpoint,
   *      the URL the end-user must be redirected to to be served the
   *      authentication form.
   *    - token_url: OAuth2 token endpoint,
   *      the URL the client will use to check the authorization_code given by
   *      user and get a token.
   *    - crypt_key: string, encryption key used to crypt information contained
   *      in the issued tokens. This is a symmetric key and must be kept secret.
   *    - sign_key: string, signature key used to sign issued tokens.
   *      This is a symmetric key and must be kept secret.
   *  - RFactory: RFactory object initialized whith minimal schema info
   *    (Grant and Client objects). cf. README file for more info.
   *  - authentication: module (or object) defining the following functions:
   *    * login: function to render the login page to end user in browser.
   *    * process_login: to process the credentials given by user.
   *      This function should use the send_grant function once user is
   *      authenticated.
   *
   */
  var sserializer = serializer.createSecureSerializer(config.crypt_key, config.sign_key);
  oauth2.set_serializer(sserializer);

  SERVER.Model = Model;
  SERVER.authentication = authentication;
  return router(function(app) {
    app.get(config.authorize_url, SERVER.authorize_endpoint);
    app.post(config.authorize_url, SERVER.authorize_endpoint);
    app.post(config.process_login_url, authentication.process_login);
    app.post(config.token_url, token_endpoint);
  });
};

var common = require('./common');

exports.set_serializer = common.set_serializer;
exports.ERRORS = common.ERRORS;
exports.create_access_token = common.create_access_token;
exports.token_info = common.token_info;
exports.check_token = common.check_token;
<commit_msg>Remove rest-mongo dependency.<commit_after>/*
 * This implements a OAuth2 server methods, as specified at:
 *  http://tools.ietf.org/html/draft-ietf-oauth-v2-10
 *
 * Only features the "web server" schema:
 *  http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-1.4.1
 *
 * Terminology:
 *  http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-1.2
 *
 */
var url = require('url')
  , querystring = require('querystring')

  , serializer = require('serializer')
  , randomString = serializer.randomString
  , router = require('connect').router

  , oauth2 = require('./common')
  ;

function redirect(res, url) {
  res.writeHead(303, {'Location': url});
  res.end();
}

function server_error(res, err) {
  res.writeHead(500, {'Content-Type': 'text/plain'});
  if(typeof err == "string") res.end(err);
  else {
    res.write('An error has occured: ' + err.message);
    res.write('\n\n');
    res.end(err.stack);
  }
}

var SERVER = exports;
// To be set by connect middleware: SERVER.RFactory, SERVER.authentication

SERVER.oauth_error = function(res, type, id) {
  /* Render a particular error.
   *
   * Arguments:
   *  - res
   *  - type: the class of the error ('eua' or 'oat').
   *  - id: the id of the error (invalid_request, invalid_client...).
   */
  res.writeHead(400, {'Content-Type': 'text/plain'});
  res.end(JSON.stringify({error: {
    type: 'OAuthException',
    message: id + ': ' + oauth2.ERRORS[type][id],
  }}));
};

var unknown_error = exports.unknown_error = function(res, err) {
  /* To call when an unknown error happens (server error).
   */
  server_error(res, err);
};

// Parameters we must/can have in different kinds of requests:
var PARAMS = exports.PARAMS = {
  eua: { // eua = end user authorization
    mandatory: ['client_id', 'response_type'],
    optional: ['state', 'scope', 'redirect_uri'],
    // possible values for response_type param:
    response_types: {'token': 1, 'code': 1, 'code_and_token': 1},
  },

  oat: { // oat = Obtaining an access token
    mandatory: ['grant_type', 'client_id', 'code', 'redirect_uri'],
    // client_secret might be provided with the 'Authorization: Basic ...' header
    optional: ['scope', 'client_secret'],
  },
};
PARAMS.eua.all = PARAMS.eua.mandatory.concat(PARAMS.eua.optional);

/**
 * Create a grant and send it to the user.
 * The code sent is of the form: grand.id + '.' + grant.code
 *
 * Arguments:
 *  - req
 *  - res
 *  - Grant:
 *  - user_id: id of the user
 *  - client_data: hash, data about the client:
 *      - client_id
 *      - redirect_uri
 *      - state: str, optional, if given, will be sent with the grant.
 *  - additional_info: optional hash, anything you want to be stored with
 *    the grant.
 *
 */
exports.send_grant = function(res, Grant, user_id, client_data, additional_info) {
  console.log(client_data);
  var grant = new Grant({
    client_id: client_data.client_id,
    time: Date.now(),
    user_id: user_id,
    code: randomString(128),
    redirect_uri: client_data.redirect_uri
  });
  if(additional_info) grant.set('additional_info', additional_info);
  grant.save(function(err, result) {
    if (err) return console.error(err);
    var qs = {code: grant.get('id') + '.' + grant.get('code')};
    if(client_data.state) qs.state = client_data.state;
    qs = querystring.stringify(qs);
    redirect(res, client_data.redirect_uri + '?' + qs);
  });
};

/**
 * Valid the grant, call callback(err, token|null)
 * token being a JSON object.
 * If valid, the grant is invalidated and cannot be used anymore.
 *
 * To be valid, a grant must exist, not be deprecated and have the right
 * associated client.
 *
 * Arguments:
 *  - Grant: Grant instance
 *  - data:
 *   - code: grant code given by client.
 *   - client_id: the client id giving the grant
 *   - redirect_uri: the redirect_uri given with the grant
 *  - callback: to be called with a token if the grant was valid,
 *    or null otherwise.
 *
 */
SERVER.valid_grant = function(Grant, data, callback) {
  var id_code = data.code.split('.');
  if(id_code.length != 2) return callback(null);
  Grant.getById(id_code[0], function(err, grant) {
    if (err) return callback(err);
    var minute_ago = Date.now() - 60000;
    if(!grant || grant.get('time') < minute_ago ||
       grant.get('client_id') != data.client_id ||
       grant.get('code') != id_code[1] ||
       grant.get('redirect_uri') != data.redirect_uri
       ) return callback('error in grant');
    var additional_info = grant.get('additional_info');
    // Delete the grant so that it cannot be used anymore:
    grant.remove(function(err, result) {
      if (err) return callback(err);
      // Generate and send an access_token to the client:
      var token = {
        access_token: oauth2.create_access_token(grant.get('user_id'), grant.get('client_id'),
                                                 additional_info)
        // optional: expires_in, refresh_token, scope
      };
      callback(null, token);
    });
  });
};

/**
 * OAuth2 token endpoint.
 * Check the authorization_code, uri_redirect and client secret, issue a token.
 *
 * POST to config.oauth2.token_url
 *
 * Arguments:
 *  - req
 *  - res
 *
 */
var token_endpoint = exports.token_endpoint = function(req, res) {
  if(!req.form) return SERVER.oauth_error(res, 'oat', 'invalid_request');
  req.form.complete(function(err, params, files) {
    if(err) return SERVER.oauth_error(res, 'oat', 'invalid_request');

    // We check there is no invalid_requet error:
    var error = false;
    params && PARAMS.oat.mandatory.forEach(function(param) {
      if(!params[param]) error = true;
    });
    if(error) return SERVER.oauth_error(res, 'oat', 'invalid_request');

    // We do only support 'authorization_code' as grant_type:
    if(params.grant_type != 'authorization_code')
      return SERVER.oauth_error(res, 'oat', 'unsupported_grant_type');

    // Check the client_secret is given once (and only once),
    // either by HTTP basic auth, or by client_secret parameter:
    var secret = req.headers['authorization'];
    if(secret) {
      if(params.client_secret)
        return SERVER.oauth_error(res, 'oat', 'invalid_request');
      params.client_secret = secret.slice(6); // remove the leading 'Basic'
    }
    else if(!params.client_secret) {
      return SERVER.oauth_error(res, 'oat', 'invalid_request');
    }
    var model = SERVER.Model
      , Client = model.Client
      , Grant = model.Grant;

    // Check the client_id exists and does have correct client_secret:
    Client.getById(params.client_id, function(err, client) {
      if (err) return unknown_error(res, err);
      if(!client || client.get('secret') != params.client_secret)
        return SERVER.oauth_error(res, 'oat', 'invalid_client');

      var data = {code: params.code,
                  client_id: client.get('id'),
                  redirect_uri: params.redirect_uri};
      SERVER.valid_grant(Grant, data, function(err, token) {
        if (err) return unknown_error(res, err);
        if (!token) return SERVER.oauth_error(res, 'oat', 'invalid_grant');
        res.writeHead(200, { 'Content-Type': 'application/json'
                           , 'Cache-Control': 'no-store'
                           });
        res.end(JSON.stringify(token));
      });
    });
  });
};

SERVER.authorize = function(params, req, res) {
  /* OAuth2 Authorize function.
   * Serve an authentication form to the end user at browser.
   *
   *  This function should only be called by oauth2.authorize
   *
   * Arguments:
   *  - params:
   *  - req
   *  - res
   *
   */
  // We check there is no invalid_requet error:
  var error = false;
  PARAMS.eua.mandatory.forEach(function(param) {
    if(!params[param]) error = true;
  });
  if(error) return SERVER.oauth_error(res, 'eua', 'invalid_request');
  if(!PARAMS.eua.response_types[params.response_type])
    return SERVER.oauth_error(res, 'eua', 'unsupported_response_type');

  // XXX: For now, we only support 'code' response type
  // which is used in case of a web server (Section 1.4.1 in oauth2 spec draft 10)
  // TODO: make it more compliant with the specs
  if(params.response_type != "code") {
    res.writeHead(501, {'Content-Type': 'text/plain'});
    res.end('Only code request type supported for now ' +
            '(schema 1.4.1 in oauth2 spec draft 10).');
    return;
  }

  var Client = SERVER.Model.Client;
  Client.getById(params.client_id, function(err, client) {
    if (err)  return unknown_error(res, err);
    if(!client) return SERVER.oauth_error(res, 'eua', 'invalid_client');
    // Check the redirect_uri is the one we know about (if we do):
    if(client.get('redirect_uri') && params.redirect_uri
       && client.get('redirect_uri') != params.redirect_uri) {
      return SERVER.oauth_error(res, 'eua', 'redirect_uri_mismatch');
    }
    var redirect_uri = client.get('redirect_uri') || params.redirect_uri;
    // Eveything is allright, ask the user to sign in.
    SERVER.authentication.login(req, res, {
      client_id: client.get('id'),
      client_name: client.get('name'),
      redirect_uri: redirect_uri,
      state: params.state
    });
  });
};

/**
 * OAuth2 Authorize end-point.
 * Serve an authentication form to the end user at browser.
 *
 *  GET or POST on config.oauth2.authorize_url
 *
 * Arguments:
 *  - req
 *  - res
 *
 */
SERVER.authorize_endpoint = function(req, res) {
  if (req.method == 'GET') {
    var params = url.parse(req.url, true).query;
    return SERVER.authorize(params, req, res);
  } else {
    if(!req.form) return SERVER.oauth_error(res, 'eua', 'invalid_request');
    req.form.complete(function(err, fields, files) {
      if(err) return SERVER.oauth_error(res, 'eua', 'invalid_request');
      SERVER.authorize(fields, req, res);
    });
  }
};

/* Returns Oauth2 server connect middleware.
 *
 * This middleware will intercept requests aiming at OAuth2 server
 * and treat them.
 *
 * Arguments:
 *  - config, hash containing:
 *    - authorize_url: end-user authorization endpoint,
 *      the URL the end-user must be redirected to to be served the
 *      authentication form.
 *    - token_url: OAuth2 token endpoint,
 *      the URL the client will use to check the authorization_code given by
 *      user and get a token.
 *    - crypt_key: string, encryption key used to crypt information contained
 *      in the issued tokens. This is a symmetric key and must be kept secret.
 *    - sign_key: string, signature key used to sign issued tokens.
 *      This is a symmetric key and must be kept secret.
 *  - RFactory: RFactory object initialized whith minimal schema info
 *    (Grant and Client objects). cf. README file for more info.
 *  - authentication: module (or object) defining the following functions:
 *    * login: function to render the login page to end user in browser.
 *    * process_login: to process the credentials given by user.
 *      This function should use the send_grant function once user is
 *      authenticated.
 *
 */
exports.connector = function(config, Model, authentication) {
  var sserializer = serializer.createSecureSerializer(config.crypt_key, config.sign_key);
  oauth2.set_serializer(sserializer);

  SERVER.Model = Model;
  SERVER.authentication = authentication;
  return router(function(app) {
    app.get(config.authorize_url, SERVER.authorize_endpoint);
    app.post(config.authorize_url, SERVER.authorize_endpoint);
    app.post(config.process_login_url, authentication.process_login);
    app.post(config.token_url, token_endpoint);
  });
};

var common = require('./common');

exports.set_serializer = common.set_serializer;
exports.ERRORS = common.ERRORS;
exports.create_access_token = common.create_access_token;
exports.token_info = common.token_info;
exports.check_token = common.check_token;
<|endoftext|>


<commit_before>/* @flow */

import type VueRouter from '../index'
import { warn } from '../util/warn'
import { inBrowser } from '../util/dom'
import { runQueue } from '../util/async'
import { START, isSameRoute } from '../util/route'
import { _Vue } from '../install'

export class History {
  router: VueRouter;
  base: string;
  current: Route;
  pending: ?Route;
  cb: (r: Route) => void;

  // implemented by sub-classes
  go: (n: number) => void;
  push: (loc: RawLocation) => void;
  replace: (loc: RawLocation) => void;
  ensureURL: (push?: boolean) => void;

  constructor (router: VueRouter, base: ?string) {
    this.router = router
    this.base = normalizeBase(base)
    // start with a route object that stands for "nowhere"
    this.current = START
    this.pending = null
  }

  listen (cb: Function) {
    this.cb = cb
  }

  transitionTo (location: RawLocation, cb?: Function) {
    const route = this.router.match(location, this.current)
    this.confirmTransition(route, () => {
      this.updateRoute(route)
      cb && cb(route)
      this.ensureURL()
    })
  }

  confirmTransition (route: Route, cb: Function) {
    const current = this.current
    if (isSameRoute(route, current)) {
      this.ensureURL()
      return
    }

    const {
      deactivated,
      activated
    } = resolveQueue(this.current.matched, route.matched)

    const queue: Array<?NavigationGuard> = [].concat(
      // in-component leave guards
      extractLeaveGuards(deactivated),
      // global before hooks
      this.router.beforeHooks,
      // enter guards
      activated.map(m => m.beforeEnter),
      // async components
      resolveAsyncComponents(activated)
    )

    this.pending = route
    const iterator = (hook: NavigationGuard, next) => {
      if (this.pending !== route) return
      hook(route, current, (to: any) => {
        if (to === false) {
          // next(false) -> abort navigation, ensure current URL
          this.ensureURL(true)
        } else if (typeof to === 'string' || typeof to === 'object') {
          // next('/') or next({ path: '/' }) -> redirect
          this.push(to)
        } else {
          // confirm transition and pass on the value
          next(to)
        }
      })
    }

    runQueue(queue, iterator, () => {
      const postEnterCbs = []
      const enterGuards = extractEnterGuards(activated, postEnterCbs, () => {
        return this.current === route
      })
      // wait until async components are resolved before
      // extracting in-component enter guards
      runQueue(enterGuards, iterator, () => {
        if (this.pending === route) {
          this.pending = null
          cb(route)
          if (this.router.app) {
            this.router.app.$nextTick(() => {
              postEnterCbs.forEach(cb => cb())
            })
          }
        }
      })
    })
  }

  updateRoute (route: Route) {
    const prev = this.current
    this.current = route
    this.cb && this.cb(route)
    this.router.afterHooks.forEach(hook => {
      hook && hook(route, prev)
    })
  }
}

function normalizeBase (base: ?string): string {
  if (!base) {
    if (inBrowser) {
      // respect <base> tag
      const baseEl = document.querySelector('base')
      base = baseEl ? baseEl.getAttribute('href') : '/'
    } else {
      base = '/'
    }
  }
  // make sure there's the starting slash
  if (base.charAt(0) !== '/') {
    base = '/' + base
  }
  // remove trailing slash
  return base.replace(/\/$/, '')
}

function resolveQueue (
  current: Array<RouteRecord>,
  next: Array<RouteRecord>
): {
  activated: Array<RouteRecord>,
  deactivated: Array<RouteRecord>
} {
  let i
  const max = Math.max(current.length, next.length)
  for (i = 0; i < max; i++) {
    if (current[i] !== next[i]) {
      break
    }
  }
  return {
    activated: next.slice(i),
    deactivated: current.slice(i)
  }
}

function extractGuard (
  def: Object | Function,
  key: string
): NavigationGuard | Array<NavigationGuard> {
  if (typeof def !== 'function') {
    // extend now so that global mixins are applied.
    def = _Vue.extend(def)
  }
  return def.options[key]
}

function extractLeaveGuards (matched: Array<RouteRecord>): Array<?Function> {
  return flatten(flatMapComponents(matched, (def, instance) => {
    const guard = extractGuard(def, 'beforeRouteLeave')
    if (guard) {
      return Array.isArray(guard)
        ? guard.map(guard => wrapLeaveGuard(guard, instance))
        : wrapLeaveGuard(guard, instance)
    }
  }).reverse())
}

function wrapLeaveGuard (
  guard: NavigationGuard,
  instance: _Vue
): NavigationGuard {
  return function routeLeaveGuard () {
    return guard.apply(instance, arguments)
  }
}

function extractEnterGuards (
  matched: Array<RouteRecord>,
  cbs: Array<Function>,
  isValid: () => boolean
): Array<?Function> {
  return flatten(flatMapComponents(matched, (def, _, match, key) => {
    const guard = extractGuard(def, 'beforeRouteEnter')
    if (guard) {
      return Array.isArray(guard)
        ? guard.map(guard => wrapEnterGuard(guard, cbs, match, key, isValid))
        : wrapEnterGuard(guard, cbs, match, key, isValid)
    }
  }))
}

function wrapEnterGuard (
  guard: NavigationGuard,
  cbs: Array<Function>,
  match: RouteRecord,
  key: string,
  isValid: () => boolean
): NavigationGuard {
  return function routeEnterGuard (to, from, next) {
    return guard(to, from, cb => {
      next(cb)
      if (typeof cb === 'function') {
        cbs.push(() => {
          // #750
          // if a router-view is wrapped with an out-in transition,
          // the instance may not have been registered at this time.
          // we will need to poll for registration until current route
          // is no longer valid.
          poll(cb, match.instances, key, isValid)
        })
      }
    })
  }
}

function poll (
  cb: any, // somehow flow cannot infer this is a function
  instances: Object,
  key: string,
  isValid: () => boolean
) {
  if (instances[key]) {
    cb(instances[key])
  } else if (isValid()) {
    setTimeout(() => {
      poll(cb, instances, key, isValid)
    }, 16)
  }
}

function resolveAsyncComponents (matched: Array<RouteRecord>): Array<?Function> {
  return flatMapComponents(matched, (def, _, match, key) => {
    // if it's a function and doesn't have Vue options attached,
    // assume it's an async component resolve function.
    // we are not using Vue's default async resolving mechanism because
    // we want to halt the navigation until the incoming component has been
    // resolved.
    if (typeof def === 'function' && !def.options) {
      return (to, from, next) => {
        const resolve = resolvedDef => {
          match.components[key] = resolvedDef
          next()
        }

        const reject = reason => {
          warn(false, `Failed to resolve async component ${key}: ${reason}`)
          next(false)
        }

        const res = def(resolve, reject)
        if (res && typeof res.then === 'function') {
          res.then(resolve, reject)
        }
      }
    }
  })
}

function flatMapComponents (
  matched: Array<RouteRecord>,
  fn: Function
): Array<?Function> {
  return flatten(matched.map(m => {
    return Object.keys(m.components).map(key => fn(
      m.components[key],
      m.instances[key],
      m, key
    ))
  }))
}

function flatten (arr) {
  return Array.prototype.concat.apply([], arr)
}
<commit_msg>support replace: true in route guards next() (close #897)<commit_after>/* @flow */

import type VueRouter from '../index'
import { warn } from '../util/warn'
import { inBrowser } from '../util/dom'
import { runQueue } from '../util/async'
import { START, isSameRoute } from '../util/route'
import { _Vue } from '../install'

export class History {
  router: VueRouter;
  base: string;
  current: Route;
  pending: ?Route;
  cb: (r: Route) => void;

  // implemented by sub-classes
  go: (n: number) => void;
  push: (loc: RawLocation) => void;
  replace: (loc: RawLocation) => void;
  ensureURL: (push?: boolean) => void;

  constructor (router: VueRouter, base: ?string) {
    this.router = router
    this.base = normalizeBase(base)
    // start with a route object that stands for "nowhere"
    this.current = START
    this.pending = null
  }

  listen (cb: Function) {
    this.cb = cb
  }

  transitionTo (location: RawLocation, cb?: Function) {
    const route = this.router.match(location, this.current)
    this.confirmTransition(route, () => {
      this.updateRoute(route)
      cb && cb(route)
      this.ensureURL()
    })
  }

  confirmTransition (route: Route, cb: Function) {
    const current = this.current
    if (isSameRoute(route, current)) {
      this.ensureURL()
      return
    }

    const {
      deactivated,
      activated
    } = resolveQueue(this.current.matched, route.matched)

    const queue: Array<?NavigationGuard> = [].concat(
      // in-component leave guards
      extractLeaveGuards(deactivated),
      // global before hooks
      this.router.beforeHooks,
      // enter guards
      activated.map(m => m.beforeEnter),
      // async components
      resolveAsyncComponents(activated)
    )

    this.pending = route
    const iterator = (hook: NavigationGuard, next) => {
      if (this.pending !== route) return
      hook(route, current, (to: any) => {
        if (to === false) {
          // next(false) -> abort navigation, ensure current URL
          this.ensureURL(true)
        } else if (typeof to === 'string' || typeof to === 'object') {
          // next('/') or next({ path: '/' }) -> redirect
          (typeof to === 'object' && to.replace) ? this.replace(to) : this.push(to)
        } else {
          // confirm transition and pass on the value
          next(to)
        }
      })
    }

    runQueue(queue, iterator, () => {
      const postEnterCbs = []
      const enterGuards = extractEnterGuards(activated, postEnterCbs, () => {
        return this.current === route
      })
      // wait until async components are resolved before
      // extracting in-component enter guards
      runQueue(enterGuards, iterator, () => {
        if (this.pending === route) {
          this.pending = null
          cb(route)
          if (this.router.app) {
            this.router.app.$nextTick(() => {
              postEnterCbs.forEach(cb => cb())
            })
          }
        }
      })
    })
  }

  updateRoute (route: Route) {
    const prev = this.current
    this.current = route
    this.cb && this.cb(route)
    this.router.afterHooks.forEach(hook => {
      hook && hook(route, prev)
    })
  }
}

function normalizeBase (base: ?string): string {
  if (!base) {
    if (inBrowser) {
      // respect <base> tag
      const baseEl = document.querySelector('base')
      base = baseEl ? baseEl.getAttribute('href') : '/'
    } else {
      base = '/'
    }
  }
  // make sure there's the starting slash
  if (base.charAt(0) !== '/') {
    base = '/' + base
  }
  // remove trailing slash
  return base.replace(/\/$/, '')
}

function resolveQueue (
  current: Array<RouteRecord>,
  next: Array<RouteRecord>
): {
  activated: Array<RouteRecord>,
  deactivated: Array<RouteRecord>
} {
  let i
  const max = Math.max(current.length, next.length)
  for (i = 0; i < max; i++) {
    if (current[i] !== next[i]) {
      break
    }
  }
  return {
    activated: next.slice(i),
    deactivated: current.slice(i)
  }
}

function extractGuard (
  def: Object | Function,
  key: string
): NavigationGuard | Array<NavigationGuard> {
  if (typeof def !== 'function') {
    // extend now so that global mixins are applied.
    def = _Vue.extend(def)
  }
  return def.options[key]
}

function extractLeaveGuards (matched: Array<RouteRecord>): Array<?Function> {
  return flatten(flatMapComponents(matched, (def, instance) => {
    const guard = extractGuard(def, 'beforeRouteLeave')
    if (guard) {
      return Array.isArray(guard)
        ? guard.map(guard => wrapLeaveGuard(guard, instance))
        : wrapLeaveGuard(guard, instance)
    }
  }).reverse())
}

function wrapLeaveGuard (
  guard: NavigationGuard,
  instance: _Vue
): NavigationGuard {
  return function routeLeaveGuard () {
    return guard.apply(instance, arguments)
  }
}

function extractEnterGuards (
  matched: Array<RouteRecord>,
  cbs: Array<Function>,
  isValid: () => boolean
): Array<?Function> {
  return flatten(flatMapComponents(matched, (def, _, match, key) => {
    const guard = extractGuard(def, 'beforeRouteEnter')
    if (guard) {
      return Array.isArray(guard)
        ? guard.map(guard => wrapEnterGuard(guard, cbs, match, key, isValid))
        : wrapEnterGuard(guard, cbs, match, key, isValid)
    }
  }))
}

function wrapEnterGuard (
  guard: NavigationGuard,
  cbs: Array<Function>,
  match: RouteRecord,
  key: string,
  isValid: () => boolean
): NavigationGuard {
  return function routeEnterGuard (to, from, next) {
    return guard(to, from, cb => {
      next(cb)
      if (typeof cb === 'function') {
        cbs.push(() => {
          // #750
          // if a router-view is wrapped with an out-in transition,
          // the instance may not have been registered at this time.
          // we will need to poll for registration until current route
          // is no longer valid.
          poll(cb, match.instances, key, isValid)
        })
      }
    })
  }
}

function poll (
  cb: any, // somehow flow cannot infer this is a function
  instances: Object,
  key: string,
  isValid: () => boolean
) {
  if (instances[key]) {
    cb(instances[key])
  } else if (isValid()) {
    setTimeout(() => {
      poll(cb, instances, key, isValid)
    }, 16)
  }
}

function resolveAsyncComponents (matched: Array<RouteRecord>): Array<?Function> {
  return flatMapComponents(matched, (def, _, match, key) => {
    // if it's a function and doesn't have Vue options attached,
    // assume it's an async component resolve function.
    // we are not using Vue's default async resolving mechanism because
    // we want to halt the navigation until the incoming component has been
    // resolved.
    if (typeof def === 'function' && !def.options) {
      return (to, from, next) => {
        const resolve = resolvedDef => {
          match.components[key] = resolvedDef
          next()
        }

        const reject = reason => {
          warn(false, `Failed to resolve async component ${key}: ${reason}`)
          next(false)
        }

        const res = def(resolve, reject)
        if (res && typeof res.then === 'function') {
          res.then(resolve, reject)
        }
      }
    }
  })
}

function flatMapComponents (
  matched: Array<RouteRecord>,
  fn: Function
): Array<?Function> {
  return flatten(matched.map(m => {
    return Object.keys(m.components).map(key => fn(
      m.components[key],
      m.instances[key],
      m, key
    ))
  }))
}

function flatten (arr) {
  return Array.prototype.concat.apply([], arr)
}
<|endoftext|>


<commit_before>var util = require('./index'),
    path = require('path'),
    url = require('url'),
    fs = require('fs'),
    express = require('express'),
    echoecho = require('echoecho'),
    exists = fs.exists || path.exists,
    server;

exports.stop = function() {
    server.close();
};

var showError = function(options, e) {
    var str = 'Grover Error\n';
    if (e.code === 'EADDRINUSE') {
        str += 'Port ' + options.port + ' is in use, try a different one!\n';
    } else if (e.code === 'EACCES') {
        str += 'You do not have access to port ' + options.port + '!\n';
    } else {
        str += e;
    }
    return str;
};

exports.showError = showError;

exports.start = function(options, callback) {
    var pre = '/',
        root = options.server,
        handler = function (req, res) {
            var u = url.parse(req.url),
                p = path.join(root, u.pathname);

            exists(p, function(x) {
                if (!x) {
                    if (echoecho.handle(req)) {
                        echoecho.serve(req, res);
                    } else {
                        res.statusCode = 404;
                        res.end('Not Found', 'utf8');
                    }
                } else {
                    fs.readFile(p, 'utf8', function(err, data) {
                        if (err) {
                            res.statusCode = 404;
                            res.end('Not Found', 'utf8');
                        } else {
                            res.statusCode = 200;
                            res.end(data, 'utf8');
                        }
                    });
                }
            });
        };

    if (!options.silent && !options.quiet) {
        util.log('  starting grover server');
        util.log('  assuming server root as ' + process.cwd());
    }
    if (options.prefix) {
        pre = options.prefix;
    }
    options.prefix = 'http:/'+'/127.0.0.1:' + options.port;

    if (!options.run) {
        util.log('listening on: ' + options.prefix);
    }

    options.paths.forEach(function(url, key) {
        url = url.replace(root, ''); //If they are full filesytem paths, we replace the root
        var URI = options.prefix + path.join(pre, url);
        URI = URI.split(path.sep).join('/');
        options.paths[key] = URI;
    });

    echoecho.paths(options.paths);

    server = express.createServer();

    server.use(express.bodyParser());

    server.get('/'+'*', handler);
    server.post('/'+'*', handler);
    server['delete']('/'+'*', handler);
    server.put('/'+'*', handler);

    server.on('error', function(e) {
        if (callback) {
            callback(e, server);
        }
        console.error(util.color(showError(options, e), 'red'));
        try {
        server.close();
        } catch (e) {}
        util.exit(1);
    });

    server.on('listening', function() {
        if (callback) {
            callback(null, server);
        }
    });
    server.listen(options.port);
    if (process.send) { //We are a child process
        process.send({ serving: true });
    }
};
<commit_msg>Better handling of server closing<commit_after>var util = require('./index'),
    path = require('path'),
    url = require('url'),
    fs = require('fs'),
    express = require('express'),
    echoecho = require('echoecho'),
    exists = fs.exists || path.exists,
    server;

var showError = function(options, e) {
    var str = 'Grover Error\n';
    if (e.code === 'EADDRINUSE') {
        str += 'Port ' + options.port + ' is in use, try a different one!\n';
    } else if (e.code === 'EACCES') {
        str += 'You do not have access to port ' + options.port + '!\n';
    } else {
        str += e;
    }
    return str;
};

exports.showError = showError;

exports.start = function(options, callback) {
    var pre = '/',
        root = options.server,
        handler = function (req, res) {
            var u = url.parse(req.url),
                p = path.join(root, u.pathname);

            exists(p, function(x) {
                if (!x) {
                    if (echoecho.handle(req)) {
                        echoecho.serve(req, res);
                    } else {
                        res.statusCode = 404;
                        res.end('Not Found', 'utf8');
                    }
                } else {
                    fs.readFile(p, 'utf8', function(err, data) {
                        if (err) {
                            res.statusCode = 404;
                            res.end('Not Found', 'utf8');
                        } else {
                            res.statusCode = 200;
                            res.end(data, 'utf8');
                        }
                    });
                }
            });
        };

    if (!options.silent && !options.quiet) {
        util.log('  starting grover server');
        util.log('  assuming server root as ' + process.cwd());
    }
    if (options.prefix) {
        pre = options.prefix;
    }
    options.prefix = 'http:/'+'/127.0.0.1:' + options.port;

    if (!options.run) {
        util.log('listening on: ' + options.prefix);
    }

    options.paths.forEach(function(url, key) {
        url = url.replace(root, ''); //If they are full filesytem paths, we replace the root
        var URI = options.prefix + path.join(pre, url);
        URI = URI.split(path.sep).join('/');
        options.paths[key] = URI;
    });

    echoecho.paths(options.paths);

    server = express.createServer();

    server.use(express.bodyParser());

    server.get('/'+'*', handler);
    server.post('/'+'*', handler);
    server['delete']('/'+'*', handler);
    server.put('/'+'*', handler);

    options.httpd = server;

    server.on('error', function(e) {
        if (callback) {
            callback(e, server);
        }
        console.error(util.color(showError(options, e), 'red'));
        try {
        server.close();
        } catch (e) {}
        util.exit(1);
    });

    server.on('listening', function() {
        if (callback) {
            callback(null, server);
        }
    });
    server.listen(options.port);
    if (process.send) { //We are a child process
        process.send({ serving: true });
    }
};
<|endoftext|>


<commit_before>import Ember from 'ember';
import DS from 'ember-data';

// Adapted from http://springember.blogspot.com.au/2014/08/using-ember-data-restadapter-with.html
export default DS.RESTSerializer.extend({
    serializeIntoHash: function(hash, type, record, options) {
        var serialized = this.serialize(record, options);

        //Remove id from the payload for new records
        //Jackson was complaining when it received a null id ...
        if (record.id == null) {
            delete serialized.id;
        }

        //remove the root element
        Ember.merge(hash, serialized);
    }
});
<commit_msg>Do not send empty fields<commit_after>import Ember from 'ember';
import DS from 'ember-data';

// Adapted from http://springember.blogspot.com.au/2014/08/using-ember-data-restadapter-with.html
export default DS.RESTSerializer.extend({
    serializeIntoHash: function(hash, type, record, options) {
        var serialized = this.serialize(record, options);

        //Remove id from the payload for new records
        //Jackson was complaining when it received a null id ...
        if (record.id == null) {
            delete serialized.id;
        }

        //Remove null values
        Object.keys(serialized).forEach(function(k) {
            if (!serialized[k]) {
                delete serialized[k];
            }
        });

        //Remove the root element
        Ember.merge(hash, serialized);
    }
});
<|endoftext|>


<commit_before>/* Copyright (c) 2006 MetaCarta, Inc., published under a modified BSD license.
 * See http://svn.openlayers.org/trunk/openlayers/repository-license.txt
 * for the full text of the license. */

/**
 * @class
 *
 * @requires OpenLayers/Control.js
 * @requires OpenLayers/Control.js
 */
OpenLayers.Control.LayerSwitcher = OpenLayers.Class.create();
OpenLayers.Control.LayerSwitcher.prototype =
  OpenLayers.Class.inherit( OpenLayers.Control, {

    /** @type String */
    activeColor: "darkblue",

  // DOM Elements

    /** @type DOMElement */
    layersDiv: null,

    /** @type DOMElement */
    baseLayersDiv: null,

    /** @type Array */
    baseLayers: null,

    /** @type DOMElement */
    dataLbl: null,

    /** @type DOMElement */
    dataLayersDiv: null,

    /** @type Array */
    dataLayers: null,

    /** @type DOMElement */
    minimizeDiv: null,

    /** @type DOMElement */
    maximizeDiv: null,

    /** @type Boolean */
    ascending: true,

    /**
    * @constructor
    */
    initialize: function(options) {
        OpenLayers.Control.prototype.initialize.apply(this, arguments);
    },

    /**
     * @param {OpenLayers.Map} map
     */
    setMap: function(map) {
        OpenLayers.Control.prototype.setMap.apply(this, arguments);

        this.map.events.register("addlayer", this, this.redraw);
        this.map.events.register("changelayer", this, this.redraw);
        this.map.events.register("removelayer", this, this.redraw);
        this.map.events.register("changebaselayer", this, this.redraw);
    },

    /**
    * @returns A reference to the DIV DOMElement containing the switcher tabs
    * @type DOMElement
    */
    draw: function() {
        OpenLayers.Control.prototype.draw.apply(this);

        // create layout divs
        this.loadContents();

        // set mode to minimize
        this.minimizeControl();

        // populate div with current info
        this.redraw();

        return this.div;
    },

    /** user specifies either "base" or "data". we then clear all the
     *    corresponding listeners, the div, and reinitialize a new array.
     *
     * @private
     *
     * @param {String} layersType Ei
     */
    clearLayersArray: function(layersType) {
        var layers = this[layersType + "Layers"];
        if (layers) {
            for(var i=0; i < layers.length; i++) {
                var layer = layers[i];
                OpenLayers.Event.stopObservingElement(layer.inputElem);
                OpenLayers.Event.stopObservingElement(layer.labelSpan);
            }
        }
        this[layersType + "LayersDiv"].innerHTML = "";
        this[layersType + "Layers"] = new Array();
    },

    /** Goes through and takes the current state of the Map and rebuilds the
     *   control to display that state. Groups base layers into a radio-button
     *   group and lists each data layer with a checkbox.
     *
     * @returns A reference to the DIV DOMElement containing the control
     * @type DOMElement
     */
    redraw: function() {

        //clear out previous layers
        this.clearLayersArray("base");
        this.clearLayersArray("data");

        var containsOverlays = false;

        var layers = this.map.layers.slice();
        if (!this.ascending) { layers.reverse(); }
        for( var i = 0; i < layers.length; i++) {
            var layer = layers[i];
            var baseLayer = layer.isBaseLayer;

            if (baseLayer || layer.displayInLayerSwitcher) {

                if (!baseLayer) {
                    containsOverlays = true;
                }

                // only check a baselayer if it is *the* baselayer, check data
                //  layers if they are visible
                var checked = (baseLayer) ? (layer == this.map.baseLayer)
                                          : layer.getVisibility();

                // create input element
                var inputElem = document.createElement("input");
                inputElem.id = "input_" + layer.name;
                inputElem.name = (baseLayer) ? "baseLayers" : layer.name;
                inputElem.type = (baseLayer) ? "radio" : "checkbox";
                inputElem.value = layer.name;
                inputElem.checked = checked;
                inputElem.defaultChecked = checked;

                if (!baseLayer && !layer.inRange) {
                    inputElem.disabled = true;
                }
                var context = {
                    'inputElem': inputElem,
                    'layer': layer,
                    'layerSwitcher': this
                }
                OpenLayers.Event.observe(inputElem, "mouseup",
                              this.onInputClick.bindAsEventListener(context));

                // create span
                var labelSpan = document.createElement("span");
                if (!baseLayer && !layer.inRange) {
                    labelSpan.style.color = "gray";
                }
                labelSpan.innerHTML = layer.name;
                labelSpan.style.verticalAlign = (baseLayer) ? "bottom"
                                                            : "baseline";
                OpenLayers.Event.observe(labelSpan, "click",
                              this.onInputClick.bindAsEventListener(context));
                // create line break
                var br = document.createElement("br");

                var groupArray = (baseLayer) ? this.baseLayers
                                             : this.dataLayers;
                groupArray.push({
                    'layer': layer,
                    'inputElem': inputElem,
                    'labelSpan': labelSpan
                });

                var groupDiv = (baseLayer) ? this.baseLayersDiv
                                           : this.dataLayersDiv;
                groupDiv.appendChild(inputElem);
                groupDiv.appendChild(labelSpan);
                groupDiv.appendChild(br);
            }
        }

        // if no overlays, dont display the overlay label
        this.dataLbl.style.display = (containsOverlays) ? "" : "none";

        return this.div;
    },

    /** A label has been clicked, check or uncheck its corresponding input
     *
     * @private
     *
     * @context
     *      {DOMElement} inputElem
     *      {OpenLayers.Control.LayerSwitcher} layerSwitcher
     *      {OpenLayers.Layer} layer
     *
     * @param {Event} e
     */

    onInputClick: function(e) {

        if (!this.inputElem.disabled) {
            if (this.inputElem.type == "radio") {
                this.inputElem.checked = true;
                this.layer.map.setBaseLayer(this.layer, true);
                this.layer.map.events.triggerEvent("changebaselayer");
            } else {
                this.inputElem.checked = !this.inputElem.checked;
                this.layerSwitcher.updateMap();
            }
        }
        OpenLayers.Event.stop(e);
    },

    /** Need to update the map accordingly whenever user clicks in either of
     *   the layers.
     *
     * @private
     *
     * @param {Event} e
     */
    onLayerClick: function(e) {
        this.updateMap();
    },

    /** Cycles through the loaded data and base layer input arrays and makes
     *   the necessary calls to the Map object such that that the map's
     *   visual state corresponds to what the user has selected in the control
     *
     * @private
     */
    updateMap: function() {

        // set the newly selected base layer
        for(var i=0; i < this.baseLayers.length; i++) {
            var layerEntry = this.baseLayers[i];
            if (layerEntry.inputElem.checked) {
                this.map.setBaseLayer(layerEntry.layer, false);
            }
        }

        // set the correct visibilities for the overlays
        for(var i=0; i < this.dataLayers.length; i++) {
            var layerEntry = this.dataLayers[i];
            layerEntry.layer.setVisibility(layerEntry.inputElem.checked, true);
        }

    },

    /** Set up the labels and divs for the control
     *
     * @param {Event} e
     */
    maximizeControl: function(e) {

        //HACK HACK HACK - find a way to auto-size this layerswitcher
        this.div.style.width = "20em";
        this.div.style.height = "";

        this.showControls(false);

        if (e != null) {
            OpenLayers.Event.stop(e);
        }
    },

    /** Hide all the contents of the control, shrink the size,
     *   add the maximize icon
     *
     * @param {Event} e
     */
    minimizeControl: function(e) {

        this.div.style.width = "0px";
        this.div.style.height = "0px";

        this.showControls(true);

        if (e != null) {
            OpenLayers.Event.stop(e);
        }
    },

    /** Hide/Show all LayerSwitcher controls depending on whether we are
     *   minimized or not
     *
     * @private
     *
     * @param {Boolean} minimize
     */
    showControls: function(minimize) {

        this.maximizeDiv.style.display = minimize ? "" : "none";
        this.minimizeDiv.style.display = minimize ? "none" : "";

        this.layersDiv.style.display = minimize ? "none" : "";
    },

    /** Set up the labels and divs for the control
     *
     */
    loadContents: function() {

        //configure main div
        this.div.style.position = "absolute";
        this.div.style.top = "10px";
        this.div.style.right = "0px";
        this.div.style.left = "";
        this.div.style.fontFamily = "sans-serif";
        this.div.style.fontWeight = "bold";
        this.div.style.marginTop = "3px";
        this.div.style.marginLeft = "3px";
        this.div.style.marginBottom = "3px";
        this.div.style.fontSize = "smaller";
        this.div.style.color = "white";
        this.div.style.backgroundColor = "transparent";

        OpenLayers.Event.observe(this.div, "mouseup",
                      this.mouseUp.bindAsEventListener(this));
        OpenLayers.Event.observe(this.div, "click",
                      this.ignoreEvent);
        OpenLayers.Event.observe(this.div, "mousedown",
                      this.mouseDown.bindAsEventListener(this));
        OpenLayers.Event.observe(this.div, "dblclick", this.ignoreEvent);

        // layers list div
        this.layersDiv = document.createElement("div");
        this.layersDiv.id = "layersDiv";
        this.layersDiv.style.paddingTop = "5px";
        this.layersDiv.style.paddingLeft = "10px";
        this.layersDiv.style.paddingBottom = "5px";
        this.layersDiv.style.paddingRight = "75px";
        this.layersDiv.style.backgroundColor = this.activeColor;

        // had to set width/height to get transparency in IE to work.
        // thanks -- http://jszen.blogspot.com/2005/04/ie6-opacity-filter-caveat.html
        //
        this.layersDiv.style.width = "100%";
        this.layersDiv.style.height = "100%";

        var baseLbl = document.createElement("div");
        baseLbl.innerHTML = "Base Layer";
        baseLbl.style.marginTop = "3px";
        baseLbl.style.marginLeft = "3px";
        baseLbl.style.marginBottom = "3px";

        this.baseLayersDiv = document.createElement("div");
        this.baseLayersDiv.style.paddingLeft = "10px";
        /*OpenLayers.Event.observe(this.baseLayersDiv, "click",
                      this.onLayerClick.bindAsEventListener(this));
        */

        this.dataLbl = document.createElement("div");
        this.dataLbl.innerHTML = "Overlays";
        this.dataLbl.style.marginTop = "3px";
        this.dataLbl.style.marginLeft = "3px";
        this.dataLbl.style.marginBottom = "3px";

        this.dataLayersDiv = document.createElement("div");
        this.dataLayersDiv.style.paddingLeft = "10px";

        if (this.ascending) {
            this.layersDiv.appendChild(baseLbl);
            this.layersDiv.appendChild(this.baseLayersDiv);
            this.layersDiv.appendChild(this.dataLbl);
            this.layersDiv.appendChild(this.dataLayersDiv);
        } else {
            this.layersDiv.appendChild(this.dataLbl);
            this.layersDiv.appendChild(this.dataLayersDiv);
            this.layersDiv.appendChild(baseLbl);
            this.layersDiv.appendChild(this.baseLayersDiv);
        }

        this.div.appendChild(this.layersDiv);

        OpenLayers.Rico.Corner.round(this.div, {corners: "tl bl",
                                        bgColor: "transparent",
                                        color: this.activeColor,
                                        blend: false});

        OpenLayers.Rico.Corner.changeOpacity(this.layersDiv, 0.75);

        var imgLocation = OpenLayers.Util.getImagesLocation();
        var sz = new OpenLayers.Size(18,18);

        // maximize button div
        var img = imgLocation + 'layer-switcher-maximize.png';
        this.maximizeDiv = OpenLayers.Util.createAlphaImageDiv(
                                    "OpenLayers_Control_MaximizeDiv",
                                    null,
                                    sz,
                                    img,
                                    "absolute");
        this.maximizeDiv.style.top = "5px";
        this.maximizeDiv.style.right = "0px";
        this.maximizeDiv.style.left = "";
        this.maximizeDiv.style.display = "none";
        OpenLayers.Event.observe(this.maximizeDiv,
                      "click",
                      this.maximizeControl.bindAsEventListener(this));

        this.div.appendChild(this.maximizeDiv);

        // minimize button div
        var img = imgLocation + 'layer-switcher-minimize.png';
        var sz = new OpenLayers.Size(18,18);
        this.minimizeDiv = OpenLayers.Util.createAlphaImageDiv(
                                    "OpenLayers_Control_MinimizeDiv",
                                    null,
                                    sz,
                                    img,
                                    "absolute");
        this.minimizeDiv.style.top = "5px";
        this.minimizeDiv.style.right = "0px";
        this.minimizeDiv.style.left = "";
        this.minimizeDiv.style.display = "none";
        OpenLayers.Event.observe(this.minimizeDiv,
                      "click",
                      this.minimizeControl.bindAsEventListener(this));

        this.div.appendChild(this.minimizeDiv);
    },

    /**
     * @private
     *
     * @param {Event} evt
     */
    ignoreEvent: function(evt) {
        OpenLayers.Event.stop(evt);
    },

    /** Register a local 'mouseDown' flag so that we'll know whether or not
     *   to ignore a mouseUp event
     *
     * @private
     *
     * @param {Event} evt
     */
    mouseDown: function(evt) {
        this.mouseDown = true;
        this.ignoreEvent(evt);
    },

    /** If the 'mouseDown' flag has been set, that means that the drag was
     *   started from within the LayerSwitcher control, and thus we can
     *   ignore the mouseup. Otherwise, let the Event continue.
     *
     * @private
     *
     * @param {Event} evt
     */
    mouseUp: function(evt) {
        if (this.mouseDown) {
            this.mouseDown = false;
            this.ignoreEvent(evt);
        }
    },

    /** @final @type String */
    CLASS_NAME: "OpenLayers.Control.LayerSwitcher"
});
<commit_msg>Destroy fix for Control.LayerSwitcher. Fixes #593.<commit_after>/* Copyright (c) 2006 MetaCarta, Inc., published under a modified BSD license.
 * See http://svn.openlayers.org/trunk/openlayers/repository-license.txt
 * for the full text of the license. */

/**
 * @class
 *
 * @requires OpenLayers/Control.js
 * @requires OpenLayers/Control.js
 */
OpenLayers.Control.LayerSwitcher = OpenLayers.Class.create();
OpenLayers.Control.LayerSwitcher.prototype =
  OpenLayers.Class.inherit( OpenLayers.Control, {

    /** @type String */
    activeColor: "darkblue",

  // DOM Elements

    /** @type DOMElement */
    layersDiv: null,

    /** @type DOMElement */
    baseLayersDiv: null,

    /** @type Array */
    baseLayers: null,

    /** @type DOMElement */
    dataLbl: null,

    /** @type DOMElement */
    dataLayersDiv: null,

    /** @type Array */
    dataLayers: null,

    /** @type DOMElement */
    minimizeDiv: null,

    /** @type DOMElement */
    maximizeDiv: null,

    /** @type Boolean */
    ascending: true,

    /**
    * @constructor
    */
    initialize: function(options) {
        OpenLayers.Control.prototype.initialize.apply(this, arguments);
    },

    /**
     *
     */
    destroy: function() {

        OpenLayers.Event.stopObservingElement(this.div);

        OpenLayers.Event.stopObservingElement(this.minimizeDiv);
        OpenLayers.Event.stopObservingElement(this.maximizeDiv);

        //clear out layers info and unregister their events
        this.clearLayersArray("base");
        this.clearLayersArray("data");

        this.map.events.unregister("addlayer", this, this.redraw);
        this.map.events.unregister("changelayer", this, this.redraw);
        this.map.events.unregister("removelayer", this, this.redraw);
        this.map.events.unregister("changebaselayer", this, this.redraw);

        OpenLayers.Control.prototype.destroy.apply(this, arguments);
    },

    /**
     * @param {OpenLayers.Map} map
     */
    setMap: function(map) {
        OpenLayers.Control.prototype.setMap.apply(this, arguments);

        this.map.events.register("addlayer", this, this.redraw);
        this.map.events.register("changelayer", this, this.redraw);
        this.map.events.register("removelayer", this, this.redraw);
        this.map.events.register("changebaselayer", this, this.redraw);
    },

    /**
    * @returns A reference to the DIV DOMElement containing the switcher tabs
    * @type DOMElement
    */
    draw: function() {
        OpenLayers.Control.prototype.draw.apply(this);

        // create layout divs
        this.loadContents();

        // set mode to minimize
        this.minimizeControl();

        // populate div with current info
        this.redraw();

        return this.div;
    },

    /** user specifies either "base" or "data". we then clear all the
     *    corresponding listeners, the div, and reinitialize a new array.
     *
     * @private
     *
     * @param {String} layersType Ei
     */
    clearLayersArray: function(layersType) {
        var layers = this[layersType + "Layers"];
        if (layers) {
            for(var i=0; i < layers.length; i++) {
                var layer = layers[i];
                OpenLayers.Event.stopObservingElement(layer.inputElem);
                OpenLayers.Event.stopObservingElement(layer.labelSpan);
            }
        }
        this[layersType + "LayersDiv"].innerHTML = "";
        this[layersType + "Layers"] = new Array();
    },

    /** Goes through and takes the current state of the Map and rebuilds the
     *   control to display that state. Groups base layers into a radio-button
     *   group and lists each data layer with a checkbox.
     *
     * @returns A reference to the DIV DOMElement containing the control
     * @type DOMElement
     */
    redraw: function() {

        //clear out previous layers
        this.clearLayersArray("base");
        this.clearLayersArray("data");

        var containsOverlays = false;

        var layers = this.map.layers.slice();
        if (!this.ascending) { layers.reverse(); }
        for( var i = 0; i < layers.length; i++) {
            var layer = layers[i];
            var baseLayer = layer.isBaseLayer;

            if (baseLayer || layer.displayInLayerSwitcher) {

                if (!baseLayer) {
                    containsOverlays = true;
                }

                // only check a baselayer if it is *the* baselayer, check data
                //  layers if they are visible
                var checked = (baseLayer) ? (layer == this.map.baseLayer)
                                          : layer.getVisibility();

                // create input element
                var inputElem = document.createElement("input");
                inputElem.id = "input_" + layer.name;
                inputElem.name = (baseLayer) ? "baseLayers" : layer.name;
                inputElem.type = (baseLayer) ? "radio" : "checkbox";
                inputElem.value = layer.name;
                inputElem.checked = checked;
                inputElem.defaultChecked = checked;

                if (!baseLayer && !layer.inRange) {
                    inputElem.disabled = true;
                }
                var context = {
                    'inputElem': inputElem,
                    'layer': layer,
                    'layerSwitcher': this
                }
                OpenLayers.Event.observe(inputElem, "mouseup",
                              this.onInputClick.bindAsEventListener(context));

                // create span
                var labelSpan = document.createElement("span");
                if (!baseLayer && !layer.inRange) {
                    labelSpan.style.color = "gray";
                }
                labelSpan.innerHTML = layer.name;
                labelSpan.style.verticalAlign = (baseLayer) ? "bottom"
                                                            : "baseline";
                OpenLayers.Event.observe(labelSpan, "click",
                              this.onInputClick.bindAsEventListener(context));
                // create line break
                var br = document.createElement("br");

                var groupArray = (baseLayer) ? this.baseLayers
                                             : this.dataLayers;
                groupArray.push({
                    'layer': layer,
                    'inputElem': inputElem,
                    'labelSpan': labelSpan
                });

                var groupDiv = (baseLayer) ? this.baseLayersDiv
                                           : this.dataLayersDiv;
                groupDiv.appendChild(inputElem);
                groupDiv.appendChild(labelSpan);
                groupDiv.appendChild(br);
            }
        }

        // if no overlays, dont display the overlay label
        this.dataLbl.style.display = (containsOverlays) ? "" : "none";

        return this.div;
    },

    /** A label has been clicked, check or uncheck its corresponding input
     *
     * @private
     *
     * @context
     *      {DOMElement} inputElem
     *      {OpenLayers.Control.LayerSwitcher} layerSwitcher
     *      {OpenLayers.Layer} layer
     *
     * @param {Event} e
     */

    onInputClick: function(e) {

        if (!this.inputElem.disabled) {
            if (this.inputElem.type == "radio") {
                this.inputElem.checked = true;
                this.layer.map.setBaseLayer(this.layer, true);
                this.layer.map.events.triggerEvent("changebaselayer");
            } else {
                this.inputElem.checked = !this.inputElem.checked;
                this.layerSwitcher.updateMap();
            }
        }
        OpenLayers.Event.stop(e);
    },

    /** Need to update the map accordingly whenever user clicks in either of
     *   the layers.
     *
     * @private
     *
     * @param {Event} e
     */
    onLayerClick: function(e) {
        this.updateMap();
    },

    /** Cycles through the loaded data and base layer input arrays and makes
     *   the necessary calls to the Map object such that that the map's
     *   visual state corresponds to what the user has selected in the control
     *
     * @private
     */
    updateMap: function() {

        // set the newly selected base layer
        for(var i=0; i < this.baseLayers.length; i++) {
            var layerEntry = this.baseLayers[i];
            if (layerEntry.inputElem.checked) {
                this.map.setBaseLayer(layerEntry.layer, false);
            }
        }

        // set the correct visibilities for the overlays
        for(var i=0; i < this.dataLayers.length; i++) {
            var layerEntry = this.dataLayers[i];
            layerEntry.layer.setVisibility(layerEntry.inputElem.checked, true);
        }

    },

    /** Set up the labels and divs for the control
     *
     * @param {Event} e
     */
    maximizeControl: function(e) {

        //HACK HACK HACK - find a way to auto-size this layerswitcher
        this.div.style.width = "20em";
        this.div.style.height = "";

        this.showControls(false);

        if (e != null) {
            OpenLayers.Event.stop(e);
        }
    },

    /** Hide all the contents of the control, shrink the size,
     *   add the maximize icon
     *
     * @param {Event} e
     */
    minimizeControl: function(e) {

        this.div.style.width = "0px";
        this.div.style.height = "0px";

        this.showControls(true);

        if (e != null) {
            OpenLayers.Event.stop(e);
        }
    },

    /** Hide/Show all LayerSwitcher controls depending on whether we are
     *   minimized or not
     *
     * @private
     *
     * @param {Boolean} minimize
     */
    showControls: function(minimize) {

        this.maximizeDiv.style.display = minimize ? "" : "none";
        this.minimizeDiv.style.display = minimize ? "none" : "";

        this.layersDiv.style.display = minimize ? "none" : "";
    },

    /** Set up the labels and divs for the control
     *
     */
    loadContents: function() {

        //configure main div
        this.div.style.position = "absolute";
        this.div.style.top = "10px";
        this.div.style.right = "0px";
        this.div.style.left = "";
        this.div.style.fontFamily = "sans-serif";
        this.div.style.fontWeight = "bold";
        this.div.style.marginTop = "3px";
        this.div.style.marginLeft = "3px";
        this.div.style.marginBottom = "3px";
        this.div.style.fontSize = "smaller";
        this.div.style.color = "white";
        this.div.style.backgroundColor = "transparent";

        OpenLayers.Event.observe(this.div, "mouseup",
                      this.mouseUp.bindAsEventListener(this));
        OpenLayers.Event.observe(this.div, "click",
                      this.ignoreEvent);
        OpenLayers.Event.observe(this.div, "mousedown",
                      this.mouseDown.bindAsEventListener(this));
        OpenLayers.Event.observe(this.div, "dblclick", this.ignoreEvent);

        // layers list div
        this.layersDiv = document.createElement("div");
        this.layersDiv.id = "layersDiv";
        this.layersDiv.style.paddingTop = "5px";
        this.layersDiv.style.paddingLeft = "10px";
        this.layersDiv.style.paddingBottom = "5px";
        this.layersDiv.style.paddingRight = "75px";
        this.layersDiv.style.backgroundColor = this.activeColor;

        // had to set width/height to get transparency in IE to work.
        // thanks -- http://jszen.blogspot.com/2005/04/ie6-opacity-filter-caveat.html
        //
        this.layersDiv.style.width = "100%";
        this.layersDiv.style.height = "100%";

        var baseLbl = document.createElement("div");
        baseLbl.innerHTML = "Base Layer";
        baseLbl.style.marginTop = "3px";
        baseLbl.style.marginLeft = "3px";
        baseLbl.style.marginBottom = "3px";

        this.baseLayersDiv = document.createElement("div");
        this.baseLayersDiv.style.paddingLeft = "10px";
        /*OpenLayers.Event.observe(this.baseLayersDiv, "click",
                      this.onLayerClick.bindAsEventListener(this));
        */

        this.dataLbl = document.createElement("div");
        this.dataLbl.innerHTML = "Overlays";
        this.dataLbl.style.marginTop = "3px";
        this.dataLbl.style.marginLeft = "3px";
        this.dataLbl.style.marginBottom = "3px";

        this.dataLayersDiv = document.createElement("div");
        this.dataLayersDiv.style.paddingLeft = "10px";

        if (this.ascending) {
            this.layersDiv.appendChild(baseLbl);
            this.layersDiv.appendChild(this.baseLayersDiv);
            this.layersDiv.appendChild(this.dataLbl);
            this.layersDiv.appendChild(this.dataLayersDiv);
        } else {
            this.layersDiv.appendChild(this.dataLbl);
            this.layersDiv.appendChild(this.dataLayersDiv);
            this.layersDiv.appendChild(baseLbl);
            this.layersDiv.appendChild(this.baseLayersDiv);
        }

        this.div.appendChild(this.layersDiv);

        OpenLayers.Rico.Corner.round(this.div, {corners: "tl bl",
                                        bgColor: "transparent",
                                        color: this.activeColor,
                                        blend: false});

        OpenLayers.Rico.Corner.changeOpacity(this.layersDiv, 0.75);

        var imgLocation = OpenLayers.Util.getImagesLocation();
        var sz = new OpenLayers.Size(18,18);

        // maximize button div
        var img = imgLocation + 'layer-switcher-maximize.png';
        this.maximizeDiv = OpenLayers.Util.createAlphaImageDiv(
                                    "OpenLayers_Control_MaximizeDiv",
                                    null,
                                    sz,
                                    img,
                                    "absolute");
        this.maximizeDiv.style.top = "5px";
        this.maximizeDiv.style.right = "0px";
        this.maximizeDiv.style.left = "";
        this.maximizeDiv.style.display = "none";
        OpenLayers.Event.observe(this.maximizeDiv,
                      "click",
                      this.maximizeControl.bindAsEventListener(this));

        this.div.appendChild(this.maximizeDiv);

        // minimize button div
        var img = imgLocation + 'layer-switcher-minimize.png';
        var sz = new OpenLayers.Size(18,18);
        this.minimizeDiv = OpenLayers.Util.createAlphaImageDiv(
                                    "OpenLayers_Control_MinimizeDiv",
                                    null,
                                    sz,
                                    img,
                                    "absolute");
        this.minimizeDiv.style.top = "5px";
        this.minimizeDiv.style.right = "0px";
        this.minimizeDiv.style.left = "";
        this.minimizeDiv.style.display = "none";
        OpenLayers.Event.observe(this.minimizeDiv,
                      "click",
                      this.minimizeControl.bindAsEventListener(this));

        this.div.appendChild(this.minimizeDiv);
    },

    /**
     * @private
     *
     * @param {Event} evt
     */
    ignoreEvent: function(evt) {
        OpenLayers.Event.stop(evt);
    },

    /** Register a local 'mouseDown' flag so that we'll know whether or not
     *   to ignore a mouseUp event
     *
     * @private
     *
     * @param {Event} evt
     */
    mouseDown: function(evt) {
        this.mouseDown = true;
        this.ignoreEvent(evt);
    },

    /** If the 'mouseDown' flag has been set, that means that the drag was
     *   started from within the LayerSwitcher control, and thus we can
     *   ignore the mouseup. Otherwise, let the Event continue.
     *
     * @private
     *
     * @param {Event} evt
     */
    mouseUp: function(evt) {
        if (this.mouseDown) {
            this.mouseDown = false;
            this.ignoreEvent(evt);
        }
    },

    /** @final @type String */
    CLASS_NAME: "OpenLayers.Control.LayerSwitcher"
});
<|endoftext|>