<commit_before>const traverse = require('traverse')
const CallType = require('@malijs/call-types')

const METHOD_PROPS = ['name', 'options', 'type', 'requestStream', 'responseStream',
  'requestName', 'responseName', 'path', 'requestType', 'responseType', 'originalName']

function getCallTypeFromCall (call) {
  const name = Object.getPrototypeOf(call).constructor.name

  if (name.indexOf('ServerUnaryCall') === 0) {
    return CallType.UNARY
  } else if (name.indexOf('ServerWritableStream') === 0) {
    return CallType.RESPONSE_STREAM
  } else if (name.indexOf('ServerReadableStream') === 0) {
    return CallType.REQUEST_STREAM
  } else if (name.indexOf('ServerDuplexStream') === 0) {
    return CallType.DUPLEX
  }
}

function getCallTypeFromDescriptor (descriptor) {
  if (!descriptor.requestStream && !descriptor.responseStream) {
    return CallType.UNARY
  } else if (!descriptor.requestStream && descriptor.responseStream) {
    return CallType.RESPONSE_STREAM
  } else if (descriptor.requestStream && !descriptor.responseStream) {
    return CallType.REQUEST_STREAM
  } else {
    return CallType.DUPLEX
  }
}

function getServiceDefintions (proto) {
  const services = {}

  traverse(proto).forEach(function (v) {
    if (isService(v)) {
      const srviceObj = v.service || v
      const vKeys = Object.keys(srviceObj)
      const name = getServiceNameFromPath(srviceObj[vKeys[0]].path)

      if (services[name]) {
        return
      }

      const shortServiceName = getShortServiceNameFromPath(srviceObj[vKeys[0]].path)

      services[name] = {
        shortServiceName: shortServiceName,
        fullServiceName: name,
        service: srviceObj,
        methods: {}
      }

      for (const k in srviceObj) {
        const m = srviceObj[k]
        services[name].methods[m.path] = METHOD_PROPS.reduce((acc, method) => {
          if (method !== 'name') {
            acc[method] = m[method]
          }
          return acc
        }, {
          name: getMethodNameFromPath(m.path),
          fullName: m.path,
          package: getPackageNameFromPath(m.path),
          service: shortServiceName
        })
      }
    } else if (v && v.type) {
      // Skip children
      this.update(v, true)
    }
  })

  return services
}

function getServiceNameFromPath (path) {
  const parts = path.split('/')
  return parts[1]
}

function getMethodNameFromPath (path) {
  const parts = path.split('/')
  return parts[parts.length - 1]
}

function getPackageNameFromPath (path) {
  const sName = getServiceNameFromPath(path)
  if (sName.indexOf('.') === -1) {
    return ''
  }

  const parts = sName.split('.')
  parts.pop()
  return parts.join('.')
}

function getShortServiceNameFromPath (path) {
  const sName = getServiceNameFromPath(path)
  if (sName.indexOf('.') === -1) {
    return sName
  }

  const parts = sName.split('.')
  return parts.pop()
}

function isService (v) {
  if (v && v.service) {
    const vKeys = Object.keys(v.service)

    return vKeys.length > 0 && v.service[vKeys[0]] && v.service[vKeys[0]].path &&
      typeof v.service[vKeys[0]].path === 'string' && v.service[vKeys[0]].path[0] === '/'
  } else if (v) {
    const vKeys = Object.keys(v)

    return vKeys.length > 0 && v[vKeys[0]] && v[vKeys[0]].path &&
      typeof v[vKeys[0]].path === 'string' && v[vKeys[0]].path[0] === '/'
  }

  return false
}

module.exports = {
  getCallTypeFromCall,
  getCallTypeFromDescriptor,
  getServiceDefintions,
  getServiceNameFromPath,
  getMethodNameFromPath,
  getPackageNameFromPath,
  getShortServiceNameFromPath
}
<commit_msg>fix: handle namespaces involving type keyword<commit_after>const traverse = require('traverse')
const CallType = require('@malijs/call-types')

const METHOD_PROPS = ['name', 'options', 'type', 'requestStream', 'responseStream',
  'requestName', 'responseName', 'path', 'requestType', 'responseType', 'originalName']

function getCallTypeFromCall (call) {
  const name = Object.getPrototypeOf(call).constructor.name

  if (name.indexOf('ServerUnaryCall') === 0) {
    return CallType.UNARY
  } else if (name.indexOf('ServerWritableStream') === 0) {
    return CallType.RESPONSE_STREAM
  } else if (name.indexOf('ServerReadableStream') === 0) {
    return CallType.REQUEST_STREAM
  } else if (name.indexOf('ServerDuplexStream') === 0) {
    return CallType.DUPLEX
  }
}

function getCallTypeFromDescriptor (descriptor) {
  if (!descriptor.requestStream && !descriptor.responseStream) {
    return CallType.UNARY
  } else if (!descriptor.requestStream && descriptor.responseStream) {
    return CallType.RESPONSE_STREAM
  } else if (descriptor.requestStream && !descriptor.responseStream) {
    return CallType.REQUEST_STREAM
  } else {
    return CallType.DUPLEX
  }
}

function getServiceDefintions (proto) {
  const services = {}

  traverse(proto).forEach(function (v) {
    if (isService(v)) {
      const srviceObj = v.service || v
      const vKeys = Object.keys(srviceObj)
      const name = getServiceNameFromPath(srviceObj[vKeys[0]].path)

      if (services[name]) {
        return
      }

      const shortServiceName = getShortServiceNameFromPath(srviceObj[vKeys[0]].path)

      services[name] = {
        shortServiceName: shortServiceName,
        fullServiceName: name,
        service: srviceObj,
        methods: {}
      }

      for (const k in srviceObj) {
        const m = srviceObj[k]
        services[name].methods[m.path] = METHOD_PROPS.reduce((acc, method) => {
          if (method !== 'name') {
            acc[method] = m[method]
          }
          return acc
        }, {
          name: getMethodNameFromPath(m.path),
          fullName: m.path,
          package: getPackageNameFromPath(m.path),
          service: shortServiceName
        })
      }
    } else if (v && v.type && typeof v.type !== 'object') {
      // Skip children
      this.update(v, true)
    }
  })

  return services
}

function getServiceNameFromPath (path) {
  const parts = path.split('/')
  return parts[1]
}

function getMethodNameFromPath (path) {
  const parts = path.split('/')
  return parts[parts.length - 1]
}

function getPackageNameFromPath (path) {
  const sName = getServiceNameFromPath(path)
  if (sName.indexOf('.') === -1) {
    return ''
  }

  const parts = sName.split('.')
  parts.pop()
  return parts.join('.')
}

function getShortServiceNameFromPath (path) {
  const sName = getServiceNameFromPath(path)
  if (sName.indexOf('.') === -1) {
    return sName
  }

  const parts = sName.split('.')
  return parts.pop()
}

function isService (v) {
  if (v && v.service) {
    const vKeys = Object.keys(v.service)

    return vKeys.length > 0 && v.service[vKeys[0]] && v.service[vKeys[0]].path &&
      typeof v.service[vKeys[0]].path === 'string' && v.service[vKeys[0]].path[0] === '/'
  } else if (v) {
    const vKeys = Object.keys(v)

    return vKeys.length > 0 && v[vKeys[0]] && v[vKeys[0]].path &&
      typeof v[vKeys[0]].path === 'string' && v[vKeys[0]].path[0] === '/'
  }

  return false
}

module.exports = {
  getCallTypeFromCall,
  getCallTypeFromDescriptor,
  getServiceDefintions,
  getServiceNameFromPath,
  getMethodNameFromPath,
  getPackageNameFromPath,
  getShortServiceNameFromPath
}
<|endoftext|>


<commit_before>"use strict";

/**
 *   Copyright 2013 Vimeo
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */
var qs_module = require('querystring');
var url_module = require('url');
var crypto_module = require('crypto');
var http_module = require('http');
var https_module = require('https');
var FileStreamer = require('./filestreamer');

module.exports.request_defaults = {
	protocol : 'https:',
	hostname : 'api.vimeo.com',
	port : 443,
	method : 'GET',
	params : {},
	headers : {
		Accept: "application/vnd.vimeo.*+json",
		'User-Agent': 'Vimeo.js/0.9.0'
	}
};

var auth_endpoints = module.exports.auth_endpoints = {
	authorization : '/oauth/authorize',
	accessToken : '/oauth/access_token',
	clientCredentials : '/oauth/authorize/client'
};

/**
 * This object is used to interface with the vimeo api
 *
 * @param {string} client_id     OAuth 2 Client Identifier
 * @param {string} client_secret OAuth 2 Client Secret
 */
var Vimeo = module.exports.Vimeo = function Vimeo (client_id, client_secret) {
	this._client_id = client_id;
	this._client_secret = client_secret;
};

Vimeo.prototype._client_id = null;
Vimeo.prototype._client_secret = null;
Vimeo.prototype.access_token = null;

/**
 * Performs an api call.
 * Can be called one of two ways.
 * 1. Url + Callback
 *     If a url is provided, we fill in the rest of the request options with defaults (GET http://api.vimeo.com/{url}).
 *
 * 2. Options + callback
 *     If an object is provided, it should match the response of url_module.parse. Path is the only required parameter.
 *
 *     hostname
 *     port
 *     query (will be applied to the url if GET, request body if POST)
 *     headers
 *     path (can include a querystring)
 *     method
 *
 * The callback takes two parameters, err and json.
 * If an error has occured, your callback will be called as callback(err);
 * If an error has not occured, your callback will be called as callback(null, json);
 *
 * @param  {String|Object} options string path (default GET), or object with path, host, port, params, headers
 * @param {Function} callback called when complete, function (err, json)
 */
Vimeo.prototype.request = function vimeo_request (options, callback) {
	var key = null;
	var client = null;

	// if a url was provided, build an options object
	if (typeof options === "string") {
		options = url_module.parse(options, true);
		options.method = "GET";
	}

	// if we don't have a path at this point, error. a path is the only required field. we have defaults for everything else important
	if (typeof options.path !== "string") {
		return callback(new Error('You must provide an api path'));
	}

	// set up the request object. we always use the options paramter first, and if no value is provided we fall back to request defaults
	var request_options = {
		protocol : options.protocol || module.exports.request_defaults.protocol,
		host : options.hostname || module.exports.request_defaults.hostname,
		port : options.port || module.exports.request_defaults.port,
		query : options.query || {},
		headers : options.headers || {},
		path : options.path,
		method : options.method || module.exports.request_defaults.method
	};

	client = request_options.protocol === 'https:' ? https_module : http_module;

	if (module.exports.request_defaults.headers) {
		for (key in module.exports.request_defaults.headers) {
			request_options.headers[key] = module.exports.request_defaults.headers[key];
		}
	}

	if (this.access_token) {
		request_options.headers.Authorization = 'Bearer ' + this.access_token;
	} else if (this._client_id && this._client_secret) {
		request_options.headers.Authorization = 'Basic ' + new Buffer(this._client_id + ':' + this._client_secret).toString('base64');
	}

	// Set proper headers for POST
	if (request_options.method === 'POST' || request_options.method === 'PATCH' || request_options.method === 'PUT') {
		request_options.headers['Content-Type'] = 'application/x-www-form-urlencoded';

	// Apply parameters to the url for GET requests
	} else if (request_options.method === 'GET') {
		var querystring = '';

		// If we have parameters, apply them to the url
		if (Object.keys(request_options.query).length) {
			if (request_options.path.indexOf('?') < 0) {
				// If the existing path does not contain any parameters, apply them as the only options
				querystring = '?' + qs_module.stringify(request_options.query);
			} else {
				// If the user already added parameters to the url, we want to add them as additional parameters
				querystring = '&' + qs_module.stringify(request_options.query);
			}
		}

		request_options.path = request_options.path + querystring;
	}

	// Perform the vimeo api request
	var req = client.request(request_options, function (res) {
		res.setEncoding('utf8');

		var buffer = '';
		res.on('data', function (data) {
			buffer += data;
		});

		if (res.statusCode >= 400) {
			// failed api calls should wait for the response to end and then call the callback with an error.
			res.on('end', function () {
				var err = new Error(buffer);
				callback(err, buffer, res.statusCode, res.headers);
			});
		} else {
			// successful api calls should wait for the response to end and then call the callback with the response body
			res.on('end', function () {
				try {
					var body = buffer.length ? JSON.parse(buffer) : {};
				} catch (e) {
					return callback(buffer, buffer, res.statusCode, res.headers);
				}
				callback(null, body, res.statusCode, res.headers);
			});
		}
	});

	if (request_options.method === "POST" || request_options.method === "PATCH" || request_options.method === "PUT") {
		req.write(qs_module.stringify(request_options.query));
	}

	// notify user of any weird connection/request errors
	req.on('error', function(e) {
		callback(e);
	});

	// send the request
	req.end();
};

/**
 * Exchange a code for an access token. This code should exist on your redirect_uri
 *
 * @param  {String} code the code provided on your redirect_uri
 * @param  {String} redirect_uri the exact redirect_uri provided to buildAuthorizationEndpoint and configured in your api app settings
 * @return {[type]}
 */
Vimeo.prototype.accessToken = function (code, redirect_uri, fn) {
	var _self = this;

	this.request({
		method : 'POST',
		hostname : module.exports.request_defaults.hostname,
		path : auth_endpoints.accessToken,
		query : {
			grant_type : 'authorization_code',
			code : code,
			redirect_uri : redirect_uri
		}
	}, function (err, body, status, headers) {
		if (err) {
			return fn(err, null, status, headers);
		} else {
			fn(null, body, status, headers);
		}
	});
};

/**
 * The first step of the authorization process.
 *
 * This function returns a url, which the user should be sent to (via redirect or link).
 * The destination allows the user to accept or deny connecting with vimeo, and accept or deny each of the scopes you requested.
 * Scopes are passed through the second parameter as an array of strings, or a space delimited list.
 *
 * Once accepted or denied, the user is redirected back to the redirect_uri.
 * If accepted, the redirect url will
 *
 * @param  {String} redirect_uri The uri that will exchange a code for an access token. Must match the uri in your app settings.
 * @param  {String} scope        An array of scopes. see https://developer.vimeo.com/api/authentication#scopes for more
 * @param  {String} state        A random state that will be returned to you on your redirect uri.
 */
Vimeo.prototype.buildAuthorizationEndpoint = function (redirect_uri, scope, state) {
	var query = {
		response_type : 'code',
		client_id : this._client_id,
		redirect_uri : redirect_uri
	};

	if (scope) {
		if (Array.isArray(scope)) {
			query.scope = scope.join(' ');
		} else {
			query.scope = scope;
		}
	} else {
		query.scope = 'public';
	}

	if (state) {
		query.state = state;
	}

	return module.exports.request_defaults.protocol + '//' + module.exports.request_defaults.hostname + auth_endpoints.authorization + '?' + qs_module.stringify(query);
};

/**
 * Generates an unauthenticated access token. This is necessary to make unauthenticated requests
 *
 * @param  {string}   scope An array of scopes. see https://developer.vimeo.com/api/authentication#scopes for more
 * @param  {Function} fn    A function that is called when the request is complete. If an error occured the first parameter will be that error, otherwise the first parameter will be null.
 */
Vimeo.prototype.generateClientCredentials = function (scope, fn) {
	var query =  {
		grant_type : 'client_credentials',
	}

	if (scope) {
		if (Array.isArray(scope)) {
			query.scope = scope.join(' ');
		} else {
			query.scope = scope;
		}
	} else {
		query.scope = 'public';
	}

	this.request({
		method : 'POST',
		hostname : module.exports.request_defaults.hostname,
		path : auth_endpoints.clientCredentials,
		query : query
	}, function (err, body, status, headers) {
		if (err) {
			return fn(err, null, status, headers);
		} else {
			fn(null, body, status, headers);
		}
	});
}

/**
 * Initiate streaming uploads
 *
 * @param  {string}   path      The path to the file you wish to upload
 * @param  {Function} callback  A function that is called when the upload is complete, or fails.
 */
Vimeo.prototype.streamingUpload = function (path, callback) {
    var _self = this;

    this.request({
        method : 'POST',
        path : '/me/videos',
        query : {
            type : 'streaming'
        }
    }, function (err, ticket, status, headers) {
        if (err) {
            return callback(err);
        }

        var file = new FileStreamer(path, ticket.upload_link_secure);

        file.ready(function () {
            _self.request({
                method : 'DELETE',
                path : ticket.complete_uri
            }, callback);
        });

        file.error(callback);
        file.upload();
    });
};
<commit_msg>Added optional Access Token as third parameter during initialisation<commit_after>"use strict";

/**
 *   Copyright 2013 Vimeo
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */
var qs_module = require('querystring');
var url_module = require('url');
var crypto_module = require('crypto');
var http_module = require('http');
var https_module = require('https');
var FileStreamer = require('./filestreamer');

module.exports.request_defaults = {
	protocol : 'https:',
	hostname : 'api.vimeo.com',
	port : 443,
	method : 'GET',
	params : {},
	headers : {
		Accept: "application/vnd.vimeo.*+json",
		'User-Agent': 'Vimeo.js/0.9.0'
	}
};

var auth_endpoints = module.exports.auth_endpoints = {
	authorization : '/oauth/authorize',
	accessToken : '/oauth/access_token',
	clientCredentials : '/oauth/authorize/client'
};

/**
 * This object is used to interface with the vimeo api
 *
 * @param {string} client_id     OAuth 2 Client Identifier
 * @param {string} client_secret OAuth 2 Client Secret
 * @param (string) access_token  OAuth 2 Optional pre-authorized access token
 */
var Vimeo = module.exports.Vimeo = function Vimeo (client_id, client_secret, access_token) {
	this._client_id = client_id;
	this._client_secret = client_secret;

	if (access_token) {
		this.access_token = access_token;
	}
};

Vimeo.prototype._client_id = null;
Vimeo.prototype._client_secret = null;
Vimeo.prototype.access_token = null;

/**
 * Performs an api call.
 * Can be called one of two ways.
 * 1. Url + Callback
 *     If a url is provided, we fill in the rest of the request options with defaults (GET http://api.vimeo.com/{url}).
 *
 * 2. Options + callback
 *     If an object is provided, it should match the response of url_module.parse. Path is the only required parameter.
 *
 *     hostname
 *     port
 *     query (will be applied to the url if GET, request body if POST)
 *     headers
 *     path (can include a querystring)
 *     method
 *
 * The callback takes two parameters, err and json.
 * If an error has occured, your callback will be called as callback(err);
 * If an error has not occured, your callback will be called as callback(null, json);
 *
 * @param  {String|Object} options string path (default GET), or object with path, host, port, params, headers
 * @param {Function} callback called when complete, function (err, json)
 */
Vimeo.prototype.request = function vimeo_request (options, callback) {
	var key = null;
	var client = null;

	// if a url was provided, build an options object
	if (typeof options === "string") {
		options = url_module.parse(options, true);
		options.method = "GET";
	}

	// if we don't have a path at this point, error. a path is the only required field. we have defaults for everything else important
	if (typeof options.path !== "string") {
		return callback(new Error('You must provide an api path'));
	}

	// set up the request object. we always use the options paramter first, and if no value is provided we fall back to request defaults
	var request_options = {
		protocol : options.protocol || module.exports.request_defaults.protocol,
		host : options.hostname || module.exports.request_defaults.hostname,
		port : options.port || module.exports.request_defaults.port,
		query : options.query || {},
		headers : options.headers || {},
		path : options.path,
		method : options.method || module.exports.request_defaults.method
	};

	client = request_options.protocol === 'https:' ? https_module : http_module;

	if (module.exports.request_defaults.headers) {
		for (key in module.exports.request_defaults.headers) {
			request_options.headers[key] = module.exports.request_defaults.headers[key];
		}
	}

	if (this.access_token) {
		request_options.headers.Authorization = 'Bearer ' + this.access_token;
	} else if (this._client_id && this._client_secret) {
		request_options.headers.Authorization = 'Basic ' + new Buffer(this._client_id + ':' + this._client_secret).toString('base64');
	}

	// Set proper headers for POST
	if (request_options.method === 'POST' || request_options.method === 'PATCH' || request_options.method === 'PUT') {
		request_options.headers['Content-Type'] = 'application/x-www-form-urlencoded';

	// Apply parameters to the url for GET requests
	} else if (request_options.method === 'GET') {
		var querystring = '';

		// If we have parameters, apply them to the url
		if (Object.keys(request_options.query).length) {
			if (request_options.path.indexOf('?') < 0) {
				// If the existing path does not contain any parameters, apply them as the only options
				querystring = '?' + qs_module.stringify(request_options.query);
			} else {
				// If the user already added parameters to the url, we want to add them as additional parameters
				querystring = '&' + qs_module.stringify(request_options.query);
			}
		}

		request_options.path = request_options.path + querystring;
	}

	// Perform the vimeo api request
	var req = client.request(request_options, function (res) {
		res.setEncoding('utf8');

		var buffer = '';
		res.on('data', function (data) {
			buffer += data;
		});

		if (res.statusCode >= 400) {
			// failed api calls should wait for the response to end and then call the callback with an error.
			res.on('end', function () {
				var err = new Error(buffer);
				callback(err, buffer, res.statusCode, res.headers);
			});
		} else {
			// successful api calls should wait for the response to end and then call the callback with the response body
			res.on('end', function () {
				try {
					var body = buffer.length ? JSON.parse(buffer) : {};
				} catch (e) {
					return callback(buffer, buffer, res.statusCode, res.headers);
				}
				callback(null, body, res.statusCode, res.headers);
			});
		}
	});

	if (request_options.method === "POST" || request_options.method === "PATCH" || request_options.method === "PUT") {
		req.write(qs_module.stringify(request_options.query));
	}

	// notify user of any weird connection/request errors
	req.on('error', function(e) {
		callback(e);
	});

	// send the request
	req.end();
};

/**
 * Exchange a code for an access token. This code should exist on your redirect_uri
 *
 * @param  {String} code the code provided on your redirect_uri
 * @param  {String} redirect_uri the exact redirect_uri provided to buildAuthorizationEndpoint and configured in your api app settings
 * @return {[type]}
 */
Vimeo.prototype.accessToken = function (code, redirect_uri, fn) {
	var _self = this;

	this.request({
		method : 'POST',
		hostname : module.exports.request_defaults.hostname,
		path : auth_endpoints.accessToken,
		query : {
			grant_type : 'authorization_code',
			code : code,
			redirect_uri : redirect_uri
		}
	}, function (err, body, status, headers) {
		if (err) {
			return fn(err, null, status, headers);
		} else {
			fn(null, body, status, headers);
		}
	});
};

/**
 * The first step of the authorization process.
 *
 * This function returns a url, which the user should be sent to (via redirect or link).
 * The destination allows the user to accept or deny connecting with vimeo, and accept or deny each of the scopes you requested.
 * Scopes are passed through the second parameter as an array of strings, or a space delimited list.
 *
 * Once accepted or denied, the user is redirected back to the redirect_uri.
 * If accepted, the redirect url will
 *
 * @param  {String} redirect_uri The uri that will exchange a code for an access token. Must match the uri in your app settings.
 * @param  {String} scope        An array of scopes. see https://developer.vimeo.com/api/authentication#scopes for more
 * @param  {String} state        A random state that will be returned to you on your redirect uri.
 */
Vimeo.prototype.buildAuthorizationEndpoint = function (redirect_uri, scope, state) {
	var query = {
		response_type : 'code',
		client_id : this._client_id,
		redirect_uri : redirect_uri
	};

	if (scope) {
		if (Array.isArray(scope)) {
			query.scope = scope.join(' ');
		} else {
			query.scope = scope;
		}
	} else {
		query.scope = 'public';
	}

	if (state) {
		query.state = state;
	}

	return module.exports.request_defaults.protocol + '//' + module.exports.request_defaults.hostname + auth_endpoints.authorization + '?' + qs_module.stringify(query);
};

/**
 * Generates an unauthenticated access token. This is necessary to make unauthenticated requests
 *
 * @param  {string}   scope An array of scopes. see https://developer.vimeo.com/api/authentication#scopes for more
 * @param  {Function} fn    A function that is called when the request is complete. If an error occured the first parameter will be that error, otherwise the first parameter will be null.
 */
Vimeo.prototype.generateClientCredentials = function (scope, fn) {
	var query =  {
		grant_type : 'client_credentials',
	}

	if (scope) {
		if (Array.isArray(scope)) {
			query.scope = scope.join(' ');
		} else {
			query.scope = scope;
		}
	} else {
		query.scope = 'public';
	}

	this.request({
		method : 'POST',
		hostname : module.exports.request_defaults.hostname,
		path : auth_endpoints.clientCredentials,
		query : query
	}, function (err, body, status, headers) {
		if (err) {
			return fn(err, null, status, headers);
		} else {
			fn(null, body, status, headers);
		}
	});
}

/**
 * Initiate streaming uploads
 *
 * @param  {string}   path      The path to the file you wish to upload
 * @param  {Function} callback  A function that is called when the upload is complete, or fails.
 */
Vimeo.prototype.streamingUpload = function (path, callback) {
    var _self = this;

    this.request({
        method : 'POST',
        path : '/me/videos',
        query : {
            type : 'streaming'
        }
    }, function (err, ticket, status, headers) {
        if (err) {
            return callback(err);
        }

        var file = new FileStreamer(path, ticket.upload_link_secure);

        file.ready(function () {
            _self.request({
                method : 'DELETE',
                path : ticket.complete_uri
            }, callback);
        });

        file.error(callback);
        file.upload();
    });
};
<|endoftext|>


<commit_before>"use strict";

/**
 *   Copyright 2013 Vimeo
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */
var qs_module = require('querystring');
var url_module = require('url');
var crypto_module = require('crypto');
var http_module = require('http');
var https_module = require('https');
var FileStreamer = require('./filestreamer');

module.exports.request_defaults = {
	protocol : 'https:',
	hostname: 'api.vimeo.com',
	port : 443,
	method : 'GET',
	query : {},
	headers : {
		'Accept': "application/vnd.vimeo.*+json;version=3.2",
		'User-Agent': 'Vimeo.js/1.2.0'
	}
};

var auth_endpoints = module.exports.auth_endpoints = {
	authorization : '/oauth/authorize',
	accessToken : '/oauth/access_token',
	clientCredentials : '/oauth/authorize/client'
};

/**
 * This object is used to interface with the vimeo api
 *
 * @param {string} client_id     OAuth 2 Client Identifier
 * @param {string} client_secret OAuth 2 Client Secret
 * @param (string) access_token  OAuth 2 Optional pre-authorized access token
 */
var Vimeo = module.exports.Vimeo = function Vimeo (client_id, client_secret, access_token) {
	this._client_id = client_id;
	this._client_secret = client_secret;

	if (access_token) {
		this.access_token = access_token;
	}
};

Vimeo.prototype._client_id = null;
Vimeo.prototype._client_secret = null;
Vimeo.prototype.access_token = null;

/**
 * Performs an api call.
 * Can be called one of two ways.
 * 1. Url + Callback
 *     If a url is provided, we fill in the rest of the request options with defaults (GET http://api.vimeo.com/{url}).
 *
 * 2. Options + callback
 *     If an object is provided, it should match the response of url_module.parse. Path is the only required parameter.
 *
 *     hostname
 *     port
 *     query (will be applied to the url if GET, request body if POST)
 *     headers
 *     path (can include a querystring)
 *     method
 *
 * The callback takes two parameters, err and json.
 * If an error has occured, your callback will be called as callback(err);
 * If an error has not occured, your callback will be called as callback(null, json);
 *
 * @param {String|Object} options string path (default GET), or object with path, host, port, query, headers
 * @param {Function} callback called when complete, function (err, json)
 */
Vimeo.prototype.request = function vimeo_request (options, callback) {
	var client = null;

	// if a url was provided, build an options object
	if (typeof options === "string") {
		options = url_module.parse(options, true);
		options.method = "GET";
	}

	// if we don't have a path at this point, error. a path is the only required field. we have defaults for everything else important
	if (typeof options.path !== "string") {
		return callback(new Error('You must provide an api path'));
	}

	// Add leading slash to path if missing
	if (options.path.charAt(0) !== '/') {
		options.path = '/' + options.path;
	}

	// Turn the provided options into options that are valid for client.request
	var request_options = this._buildRequestOptions(options);

	// Locate the proper client from the request protocol
	client = request_options.protocol === 'https:' ? https_module : http_module;

	// Write the request body
	if (['POST','PATCH','PUT','DELETE'].indexOf(request_options.method) !== -1) {
		if (request_options.headers['Content-Type'] === 'application/json') {
			request_options.body = JSON.stringify(options.query);
		} else {
			request_options.body = qs_module.stringify(options.query);
		}

		if (request_options.body) {
			request_options.headers['Content-Length'] = Buffer.byteLength(request_options.body, 'utf8');
		} else {
			request_options.headers['Content-Length'] = 0;
		}
	}

	// Perform the vimeo api request
	var req = client.request(request_options, this._handleRequest(callback));

	// Write the request body
	if (request_options.body) {
		req.write(request_options.body);
	}

	// notify user of any weird connection/request errors
	req.on('error', function(e) {
		callback(e);
	});

	// send the request
	req.end();
};

/**
 * Creates the standard request handler for http requests
 *
 * @param  {Function} callback
 * @return {Function}
 */
Vimeo.prototype._handleRequest = function (callback) {
	return function (res) {

		res.setEncoding('utf8');

		var buffer = '';

		res.on('readable', function () {
			buffer += res.read() || '';
		});

		if (res.statusCode >= 400) {
			// failed api calls should wait for the response to end and then call the callback with an error.
			res.on('end', function () {
				var err = new Error(buffer);
				callback(err, buffer, res.statusCode, res.headers);
			});
		} else {
			// successful api calls should wait for the response to end and then call the callback with the response body
			res.on('end', function () {
				try {
					var body = buffer.length ? JSON.parse(buffer) : {};
				} catch (e) {
					return callback(buffer, buffer, res.statusCode, res.headers);
				}
				callback(null, body, res.statusCode, res.headers);
			});
		}
	};
};

/**
 * Merge the request options defaults into the request options
 *
 * @param  {Object} options
 * @return {Object}
 */
Vimeo.prototype._buildRequestOptions = function (options) {
	// set up the request object. we always use the options paramter first, and if no value is provided we fall back to request defaults
	var request_options = this._applyDefaultRequestOptions(options);

	// Apply the access tokens
	if (this.access_token) {
		request_options.headers.Authorization = 'Bearer ' + this.access_token;
	} else if (this._client_id && this._client_secret) {
		request_options.headers.Authorization = 'Basic ' + new Buffer(this._client_id + ':' + this._client_secret).toString('base64');
	}

	// Set proper headers for POST, PATCH and PUT bodies
	if (['POST','PATCH','PUT','DELETE'].indexOf(request_options.method) !== -1 && !request_options.headers['Content-Type']) {
		request_options.headers['Content-Type'] = 'application/json';

	// Apply parameters to the url for GET requests
	} else if (request_options.method === 'GET') {
		request_options.path = this._applyQuerystringParams(request_options, options);
	}

	return request_options;
}

/**
 * Create an object of request options based on the provided list of options, and the request defaults.
 *
 * @param  {Object} options
 * @return {Object}
 */
Vimeo.prototype._applyDefaultRequestOptions = function (options) {
	var request_options = {
		protocol : options.protocol || module.exports.request_defaults.protocol,
		host : options.hostname || module.exports.request_defaults.hostname,
		port : options.port || module.exports.request_defaults.port,
		method : options.method || module.exports.request_defaults.method,
		headers : options.headers || {},
		body : '',
		path : options.path
	};
	var key = null;

	// Apply the default headers
	if (module.exports.request_defaults.headers) {
		for (key in module.exports.request_defaults.headers) {
			if (!request_options.headers[key]) {
				request_options.headers[key] = module.exports.request_defaults.headers[key];
			}
		}
	}

	return request_options;
}

/**
 * Apply the query parameter onto the final request url
 *
 * @param  {Object} request_options
 * @param  {Object} options
 * @return {String}
 */
Vimeo.prototype._applyQuerystringParams = function (request_options, options) {
	var querystring = '';

	if (!options.query) {
		return request_options.path;
	}

	// If we have parameters, apply them to the url
	if (Object.keys(options.query).length) {
		if (request_options.path.indexOf('?') < 0) {
			// If the existing path does not contain any parameters, apply them as the only options
			querystring = '?' + qs_module.stringify(options.query);
		} else {
			// If the user already added parameters to the url, we want to add them as additional parameters
			querystring = '&' + qs_module.stringify(options.query);
		}
	}

	return request_options.path + querystring;
}

/**
 * Exchange a code for an access token. This code should exist on your redirect_uri
 *
 * @param  {String} code the code provided on your redirect_uri
 * @param  {String} redirect_uri the exact redirect_uri provided to buildAuthorizationEndpoint and configured in your api app settings
 * @return {null}
 */
Vimeo.prototype.accessToken = function (code, redirect_uri, fn) {
	var _self = this;

	this.request({
		method : 'POST',
		hostname : module.exports.request_defaults.hostname,
		path : auth_endpoints.accessToken,
		query : {
			grant_type : 'authorization_code',
			code : code,
			redirect_uri : redirect_uri
		},
		headers : {
			'Content-Type' : 'application/x-www-form-urlencoded'
		}
	}, function (err, body, status, headers) {
		if (err) {
			return fn(err, null, status, headers);
		} else {
			fn(null, body, status, headers);
		}
	});
};

/**
 * The first step of the authorization process.
 *
 * This function returns a url, which the user should be sent to (via redirect or link).
 * The destination allows the user to accept or deny connecting with vimeo, and accept or deny each of the scopes you requested.
 * Scopes are passed through the second parameter as an array of strings, or a space delimited list.
 *
 * Once accepted or denied, the user is redirected back to the redirect_uri.
 * If accepted, the redirect url will
 *
 * @param  {String} redirect_uri The uri that will exchange a code for an access token. Must match the uri in your app settings.
 * @param  {String} scope        An array of scopes. see https://developer.vimeo.com/api/authentication#scopes for more
 * @param  {String} state        A random state that will be returned to you on your redirect uri.
 */
Vimeo.prototype.buildAuthorizationEndpoint = function (redirect_uri, scope, state) {
	var query = {
		response_type : 'code',
		client_id : this._client_id,
		redirect_uri : redirect_uri
	};

	if (scope) {
		if (Array.isArray(scope)) {
			query.scope = scope.join(' ');
		} else {
			query.scope = scope;
		}
	} else {
		query.scope = 'public';
	}

	if (state) {
		query.state = state;
	}

	return module.exports.request_defaults.protocol + '//' + module.exports.request_defaults.hostname + auth_endpoints.authorization + '?' + qs_module.stringify(query);
};

/**
 * Generates an unauthenticated access token. This is necessary to make unauthenticated requests
 *
 * @param  {string}   scope An array of scopes. see https://developer.vimeo.com/api/authentication#scopes for more
 * @param  {Function} fn    A function that is called when the request is complete. If an error occured the first parameter will be that error, otherwise the first parameter will be null.
 */
Vimeo.prototype.generateClientCredentials = function (scope, fn) {
	var query =  {
		grant_type : 'client_credentials',
	}

	if (scope) {
		if (Array.isArray(scope)) {
			query.scope = scope.join(' ');
		} else {
			query.scope = scope;
		}
	} else {
		query.scope = 'public';
	}

	this.request({
		method : 'POST',
		hostname : module.exports.request_defaults.hostname,
		path : auth_endpoints.clientCredentials,
		query : query,
		headers : {
			'Content-Type' : 'application/x-www-form-urlencoded'
		}
	}, function (err, body, status, headers) {
		if (err) {
			return fn(err, null, status, headers);
		} else {
			fn(null, body, status, headers);
		}
	});
}

/**
 * Initiate streaming uploads
 *
 * @param  {string}   path      The path to the file you wish to upload
 * @param  {Function} callback  A function that is called when the upload is complete, or fails.
 */
Vimeo.prototype.streamingUpload = function (path, video_uri, callback, progress_callback) {
    var _self = this;

    if (typeof video_uri === 'function') {
		progress_callback = callback;
		callback = video_uri;
		video_uri = undefined;
	}

    var options = {
        method : video_uri ? 'PUT' : 'POST',
        path : video_uri ? video_uri + '/files' : '/me/videos',
        query : {
            type : 'streaming'
        }
    };

    this.request(options, function (err, ticket, status, headers) {
        if (err) {
            return callback(new Error('Requesting ticket err: ' + err.message));
        }

        var file = new FileStreamer(path, ticket.upload_link_secure, ticket.ticket_id, progress_callback);

        file.ready(function () {
            _self.request({
                method : 'DELETE',
                path : ticket.complete_uri
            }, function (err, body, status, headers) {
            	if (err) {
            		callback(new Error('Completing upload failed: ' + err.message), body, status, headers);
            	} else {
            		callback(err, body, status, headers);
            	}
            });
        });

        file.error(callback);
        file.upload();
    });
};
<commit_msg>log ticket_id<commit_after>"use strict";

/**
 *   Copyright 2013 Vimeo
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */
var qs_module = require('querystring');
var url_module = require('url');
var crypto_module = require('crypto');
var http_module = require('http');
var https_module = require('https');
var FileStreamer = require('./filestreamer');

module.exports.request_defaults = {
	protocol : 'https:',
	hostname: 'api.vimeo.com',
	port : 443,
	method : 'GET',
	query : {},
	headers : {
		'Accept': "application/vnd.vimeo.*+json;version=3.2",
		'User-Agent': 'Vimeo.js/1.2.0'
	}
};

var auth_endpoints = module.exports.auth_endpoints = {
	authorization : '/oauth/authorize',
	accessToken : '/oauth/access_token',
	clientCredentials : '/oauth/authorize/client'
};

/**
 * This object is used to interface with the vimeo api
 *
 * @param {string} client_id     OAuth 2 Client Identifier
 * @param {string} client_secret OAuth 2 Client Secret
 * @param (string) access_token  OAuth 2 Optional pre-authorized access token
 */
var Vimeo = module.exports.Vimeo = function Vimeo (client_id, client_secret, access_token) {
	this._client_id = client_id;
	this._client_secret = client_secret;

	if (access_token) {
		this.access_token = access_token;
	}
};

Vimeo.prototype._client_id = null;
Vimeo.prototype._client_secret = null;
Vimeo.prototype.access_token = null;

/**
 * Performs an api call.
 * Can be called one of two ways.
 * 1. Url + Callback
 *     If a url is provided, we fill in the rest of the request options with defaults (GET http://api.vimeo.com/{url}).
 *
 * 2. Options + callback
 *     If an object is provided, it should match the response of url_module.parse. Path is the only required parameter.
 *
 *     hostname
 *     port
 *     query (will be applied to the url if GET, request body if POST)
 *     headers
 *     path (can include a querystring)
 *     method
 *
 * The callback takes two parameters, err and json.
 * If an error has occured, your callback will be called as callback(err);
 * If an error has not occured, your callback will be called as callback(null, json);
 *
 * @param {String|Object} options string path (default GET), or object with path, host, port, query, headers
 * @param {Function} callback called when complete, function (err, json)
 */
Vimeo.prototype.request = function vimeo_request (options, callback) {
	var client = null;

	// if a url was provided, build an options object
	if (typeof options === "string") {
		options = url_module.parse(options, true);
		options.method = "GET";
	}

	// if we don't have a path at this point, error. a path is the only required field. we have defaults for everything else important
	if (typeof options.path !== "string") {
		return callback(new Error('You must provide an api path'));
	}

	// Add leading slash to path if missing
	if (options.path.charAt(0) !== '/') {
		options.path = '/' + options.path;
	}

	// Turn the provided options into options that are valid for client.request
	var request_options = this._buildRequestOptions(options);

	// Locate the proper client from the request protocol
	client = request_options.protocol === 'https:' ? https_module : http_module;

	// Write the request body
	if (['POST','PATCH','PUT','DELETE'].indexOf(request_options.method) !== -1) {
		if (request_options.headers['Content-Type'] === 'application/json') {
			request_options.body = JSON.stringify(options.query);
		} else {
			request_options.body = qs_module.stringify(options.query);
		}

		if (request_options.body) {
			request_options.headers['Content-Length'] = Buffer.byteLength(request_options.body, 'utf8');
		} else {
			request_options.headers['Content-Length'] = 0;
		}
	}

	// Perform the vimeo api request
	var req = client.request(request_options, this._handleRequest(callback));

	// Write the request body
	if (request_options.body) {
		req.write(request_options.body);
	}

	// notify user of any weird connection/request errors
	req.on('error', function(e) {
		callback(e);
	});

	// send the request
	req.end();
};

/**
 * Creates the standard request handler for http requests
 *
 * @param  {Function} callback
 * @return {Function}
 */
Vimeo.prototype._handleRequest = function (callback) {
	return function (res) {

		res.setEncoding('utf8');

		var buffer = '';

		res.on('readable', function () {
			buffer += res.read() || '';
		});

		if (res.statusCode >= 400) {
			// failed api calls should wait for the response to end and then call the callback with an error.
			res.on('end', function () {
				var err = new Error(buffer);
				callback(err, buffer, res.statusCode, res.headers);
			});
		} else {
			// successful api calls should wait for the response to end and then call the callback with the response body
			res.on('end', function () {
				try {
					var body = buffer.length ? JSON.parse(buffer) : {};
				} catch (e) {
					return callback(buffer, buffer, res.statusCode, res.headers);
				}
				callback(null, body, res.statusCode, res.headers);
			});
		}
	};
};

/**
 * Merge the request options defaults into the request options
 *
 * @param  {Object} options
 * @return {Object}
 */
Vimeo.prototype._buildRequestOptions = function (options) {
	// set up the request object. we always use the options paramter first, and if no value is provided we fall back to request defaults
	var request_options = this._applyDefaultRequestOptions(options);

	// Apply the access tokens
	if (this.access_token) {
		request_options.headers.Authorization = 'Bearer ' + this.access_token;
	} else if (this._client_id && this._client_secret) {
		request_options.headers.Authorization = 'Basic ' + new Buffer(this._client_id + ':' + this._client_secret).toString('base64');
	}

	// Set proper headers for POST, PATCH and PUT bodies
	if (['POST','PATCH','PUT','DELETE'].indexOf(request_options.method) !== -1 && !request_options.headers['Content-Type']) {
		request_options.headers['Content-Type'] = 'application/json';

	// Apply parameters to the url for GET requests
	} else if (request_options.method === 'GET') {
		request_options.path = this._applyQuerystringParams(request_options, options);
	}

	return request_options;
}

/**
 * Create an object of request options based on the provided list of options, and the request defaults.
 *
 * @param  {Object} options
 * @return {Object}
 */
Vimeo.prototype._applyDefaultRequestOptions = function (options) {
	var request_options = {
		protocol : options.protocol || module.exports.request_defaults.protocol,
		host : options.hostname || module.exports.request_defaults.hostname,
		port : options.port || module.exports.request_defaults.port,
		method : options.method || module.exports.request_defaults.method,
		headers : options.headers || {},
		body : '',
		path : options.path
	};
	var key = null;

	// Apply the default headers
	if (module.exports.request_defaults.headers) {
		for (key in module.exports.request_defaults.headers) {
			if (!request_options.headers[key]) {
				request_options.headers[key] = module.exports.request_defaults.headers[key];
			}
		}
	}

	return request_options;
}

/**
 * Apply the query parameter onto the final request url
 *
 * @param  {Object} request_options
 * @param  {Object} options
 * @return {String}
 */
Vimeo.prototype._applyQuerystringParams = function (request_options, options) {
	var querystring = '';

	if (!options.query) {
		return request_options.path;
	}

	// If we have parameters, apply them to the url
	if (Object.keys(options.query).length) {
		if (request_options.path.indexOf('?') < 0) {
			// If the existing path does not contain any parameters, apply them as the only options
			querystring = '?' + qs_module.stringify(options.query);
		} else {
			// If the user already added parameters to the url, we want to add them as additional parameters
			querystring = '&' + qs_module.stringify(options.query);
		}
	}

	return request_options.path + querystring;
}

/**
 * Exchange a code for an access token. This code should exist on your redirect_uri
 *
 * @param  {String} code the code provided on your redirect_uri
 * @param  {String} redirect_uri the exact redirect_uri provided to buildAuthorizationEndpoint and configured in your api app settings
 * @return {null}
 */
Vimeo.prototype.accessToken = function (code, redirect_uri, fn) {
	var _self = this;

	this.request({
		method : 'POST',
		hostname : module.exports.request_defaults.hostname,
		path : auth_endpoints.accessToken,
		query : {
			grant_type : 'authorization_code',
			code : code,
			redirect_uri : redirect_uri
		},
		headers : {
			'Content-Type' : 'application/x-www-form-urlencoded'
		}
	}, function (err, body, status, headers) {
		if (err) {
			return fn(err, null, status, headers);
		} else {
			fn(null, body, status, headers);
		}
	});
};

/**
 * The first step of the authorization process.
 *
 * This function returns a url, which the user should be sent to (via redirect or link).
 * The destination allows the user to accept or deny connecting with vimeo, and accept or deny each of the scopes you requested.
 * Scopes are passed through the second parameter as an array of strings, or a space delimited list.
 *
 * Once accepted or denied, the user is redirected back to the redirect_uri.
 * If accepted, the redirect url will
 *
 * @param  {String} redirect_uri The uri that will exchange a code for an access token. Must match the uri in your app settings.
 * @param  {String} scope        An array of scopes. see https://developer.vimeo.com/api/authentication#scopes for more
 * @param  {String} state        A random state that will be returned to you on your redirect uri.
 */
Vimeo.prototype.buildAuthorizationEndpoint = function (redirect_uri, scope, state) {
	var query = {
		response_type : 'code',
		client_id : this._client_id,
		redirect_uri : redirect_uri
	};

	if (scope) {
		if (Array.isArray(scope)) {
			query.scope = scope.join(' ');
		} else {
			query.scope = scope;
		}
	} else {
		query.scope = 'public';
	}

	if (state) {
		query.state = state;
	}

	return module.exports.request_defaults.protocol + '//' + module.exports.request_defaults.hostname + auth_endpoints.authorization + '?' + qs_module.stringify(query);
};

/**
 * Generates an unauthenticated access token. This is necessary to make unauthenticated requests
 *
 * @param  {string}   scope An array of scopes. see https://developer.vimeo.com/api/authentication#scopes for more
 * @param  {Function} fn    A function that is called when the request is complete. If an error occured the first parameter will be that error, otherwise the first parameter will be null.
 */
Vimeo.prototype.generateClientCredentials = function (scope, fn) {
	var query =  {
		grant_type : 'client_credentials',
	}

	if (scope) {
		if (Array.isArray(scope)) {
			query.scope = scope.join(' ');
		} else {
			query.scope = scope;
		}
	} else {
		query.scope = 'public';
	}

	this.request({
		method : 'POST',
		hostname : module.exports.request_defaults.hostname,
		path : auth_endpoints.clientCredentials,
		query : query,
		headers : {
			'Content-Type' : 'application/x-www-form-urlencoded'
		}
	}, function (err, body, status, headers) {
		if (err) {
			return fn(err, null, status, headers);
		} else {
			fn(null, body, status, headers);
		}
	});
}

/**
 * Initiate streaming uploads
 *
 * @param  {string}   path      The path to the file you wish to upload
 * @param  {Function} callback  A function that is called when the upload is complete, or fails.
 */
Vimeo.prototype.streamingUpload = function (path, video_uri, callback, progress_callback) {
    var _self = this;

    if (typeof video_uri === 'function') {
		progress_callback = callback;
		callback = video_uri;
		video_uri = undefined;
	}

    var options = {
        method : video_uri ? 'PUT' : 'POST',
        path : video_uri ? video_uri + '/files' : '/me/videos',
        query : {
            type : 'streaming'
        }
    };

    this.request(options, function (err, ticket, status, headers) {
        if (err) {
            return callback(new Error('Requesting ticket err: ' + err.message));
        }

        var file = new FileStreamer(path, ticket.upload_link_secure, ticket.ticket_id, progress_callback);

        file.ready(function () {
            _self.request({
                method : 'DELETE',
                path : ticket.complete_uri
            }, function (err, body, status, headers) {
            	if (err) {
            		callback(new Error('Completing upload failed for ticket ' + ticket.ticket_id + ': ' + err.message), body, status, headers);
            	} else {
            		callback(err, body, status, headers);
            	}
            });
        });

        file.error(callback);
        file.upload();
    });
};
<|endoftext|>


<commit_before>"use strict"

module.exports = vectorizeText
module.exports.processPixels = processPixels

var surfaceNets = require('surface-nets')
var ndarray = require('ndarray')
var simplify = require('simplify-planar-graph')
var cleanPSLG = require('clean-pslg')
var cdt2d = require('cdt2d')
var toPolygonCrappy = require('planar-graph-to-polyline')

var TAG_bold = "b"
var CHR_bold = 'b'

var TAG_italic = "i"
var CHR_italic = 'i'

var TAG_super = "sup"
var CHR_super = 'P'

var TAG_sub = "sub"
var CHR_sub = 'B'

function parseTag(tag, TAG_CHR, str, map) {

  var opnTag =  "<"  + tag + ">"
  var clsTag =  "</" + tag + ">"

  var nOPN = opnTag.length
  var nCLS = clsTag.length

  var nStr = str.length

  var a = 0
  var b = -nCLS
  while (a > -1) {
    a = str.indexOf(opnTag, b + nCLS)
    if(a === -1) break

    b = str.indexOf(clsTag, a + nOPN)
    if(b === -1) break

    if(b <= a) break

    for(var i = a; i < b + nCLS; ++i){
      if((i < a + nOPN) || (i >= b)) {
        map[i] = null
      } else {
        if(map[i] !== null) {
          if(map[i].indexOf(TAG_CHR) === -1) map[i] += TAG_CHR
        }
      }
    }
  }

  return map
}

function transformPositions(positions, options, size) {
  var align = options.textAlign || "start"
  var baseline = options.textBaseline || "alphabetic"

  var lo = [1<<30, 1<<30]
  var hi = [0,0]
  var n = positions.length
  for(var i=0; i<n; ++i) {
    var p = positions[i]
    for(var j=0; j<2; ++j) {
      lo[j] = Math.min(lo[j], p[j])|0
      hi[j] = Math.max(hi[j], p[j])|0
    }
  }

  var xShift = 0
  switch(align) {
    case "center":
      xShift = -0.5 * (lo[0] + hi[0])
    break

    case "right":
    case "end":
      xShift = -hi[0]
    break

    case "left":
    case "start":
      xShift = -lo[0]
    break

    default:
      throw new Error("vectorize-text: Unrecognized textAlign: '" + align + "'")
  }

  var yShift = 0
  switch(baseline) {
    case "hanging":
    case "top":
      yShift = -lo[1]
    break

    case "middle":
      yShift = -0.5 * (lo[1] + hi[1])
    break

    case "alphabetic":
    case "ideographic":
      yShift = -3 * size
    break

    case "bottom":
      yShift = -hi[1]
    break

    default:
      throw new Error("vectorize-text: Unrecoginized textBaseline: '" + baseline + "'")
  }

  var scale = 1.0 / size
  if("lineHeight" in options) {
    scale *= +options.lineHeight
  } else if("width" in options) {
    scale = options.width / (hi[0] - lo[0])
  } else if("height" in options) {
    scale = options.height / (hi[1] - lo[1])
  }

  return positions.map(function(p) {
    return [ scale * (p[0] + xShift), scale * (p[1] + yShift) ]
  })
}

function getPixels(canvas, context, raw_str, fontSize, lineSpacing) {

  raw_str = raw_str.replace(/\<br\>/g, '\n')

  var activeStyle = ""
  var map = []
  for (j = 0; j < raw_str.length; ++j) {
    map[j] = activeStyle
  }
  map = parseTag(TAG_bold,
                 CHR_bold, raw_str, map)
  map = parseTag(TAG_italic,
                 CHR_italic, raw_str, map)
  map = parseTag(TAG_super,
                 CHR_super, raw_str, map)
  map = parseTag(TAG_sub,
                 CHR_sub, raw_str, map)

  var allStyles = []
  var plainText = ""
  for(j = 0; j < raw_str.length; ++j) {
    if(map[j] !== null) {
      plainText += raw_str[j]
      allStyles.push(map[j])
    }
  }

  var allTexts = plainText.split('\n')

  var numberOfLines = allTexts.length
  var lineHeight = Math.round(lineSpacing * fontSize)
  var offsetX = fontSize
  var offsetY = fontSize * 2
  var maxWidth = 0
  var minHeight = numberOfLines * lineHeight + offsetY

  if(canvas.height < minHeight) {
    canvas.height = minHeight
  }

  context.fillStyle = "#000"
  context.fillRect(0, 0, canvas.width, canvas.height)

  context.fillStyle = "#fff"
  var i, j, xPos, yPos, zPos
  var nDone = 0

  for(i = 0; i < numberOfLines; ++i) {

    var txt = allTexts[i] + '\n'
    xPos = 0
    yPos = i * lineHeight
    zPos = fontSize

    var buffer = ""
    function writeBuffer() {
      if(buffer !== "") {
        var delta = context.measureText(buffer).width

        context.fillText(buffer, offsetX + xPos, offsetY + yPos)
        xPos += delta
      }
    }

    function changeStyle(oldStyle, newStyle) {

      function getTextFontSize() {
        return "" + Math.round(zPos) + "px ";
      }

      var ctxFont = "" + context.font;

      var wasSub = (oldStyle.indexOf(CHR_sub) > -1)
      var is_Sub = (newStyle.indexOf(CHR_sub) > -1)

      if(!wasSub && is_Sub) {
        ctxFont = ctxFont.replace(getTextFontSize(), "?px ")
        zPos /= 2
        ctxFont = ctxFont.replace("?px ", getTextFontSize())
      }
      if(wasSub && !is_Sub) {
        ctxFont = ctxFont.replace(getTextFontSize(), "?px ")
        zPos *= 2
        ctxFont = ctxFont.replace("?px ", getTextFontSize())
      }

      var wasSuper = (oldStyle.indexOf(CHR_super) > -1)
      var is_Super = (newStyle.indexOf(CHR_super) > -1)

      if(!wasSuper && is_Super) {
        ctxFont = ctxFont.replace(getTextFontSize(), "?px ")
        zPos /= 2
        ctxFont = ctxFont.replace("?px ", getTextFontSize())
        yPos -= 0.25 * lineHeight
      }
      if(wasSuper && !is_Super) {
        ctxFont = ctxFont.replace(getTextFontSize(), "?px ")
        zPos *= 2
        ctxFont = ctxFont.replace("?px ", getTextFontSize())
        yPos += 0.25 * lineHeight
      }

      var wasBold = (oldStyle.indexOf(CHR_bold) > -1)
      var is_Bold = (newStyle.indexOf(CHR_bold) > -1)

      if(!wasBold && is_Bold) {
        if(wasItalic) {
          ctxFont = ctxFont.replace("italic ", "italic bold ")
        } else {
          ctxFont = "bold " + ctxFont
        }
      }
      if(wasBold && !is_Bold) {
        ctxFont = ctxFont.replace("bold ", '')
      }

      var wasItalic = (oldStyle.indexOf(CHR_italic) > -1)
      var is_Italic = (newStyle.indexOf(CHR_italic) > -1)

      if(!wasItalic && is_Italic) {
        ctxFont = "italic " + ctxFont
      }
      if(wasItalic && !is_Italic) {
        ctxFont = ctxFont.replace("italic ", '')
      }

      context.font = ctxFont
    }

    for(j = 0; j < txt.length; ++j) {
      var style = (j + nDone < allStyles.length) ? allStyles[j + nDone] : allStyles[allStyles.length - 1]
      if(activeStyle === style) {
        buffer += txt[j]
      } else {
        writeBuffer()
        buffer = txt[j]

        changeStyle(activeStyle, style)
        activeStyle = style
      }
    }
    writeBuffer()

    nDone += txt.length

    var width = Math.round(xPos + 2 * offsetX) | 0
    if(maxWidth < width) maxWidth = width
  }

  //Cut pixels from image
  var xCut = maxWidth
  var yCut = offsetY + lineHeight * numberOfLines
  var pixels = ndarray(context.getImageData(0, 0, xCut, yCut).data, [yCut, xCut, 4])
  return pixels.pick(-1, -1, 0).transpose(1, 0)
}

function getContour(pixels, doSimplify) {
  var contour = surfaceNets(pixels, 128)
  if(doSimplify) {
    return simplify(contour.cells, contour.positions, 0.25)
  }
  return {
    edges: contour.cells,
    positions: contour.positions
  }
}

function processPixelsImpl(pixels, options, size, simplify) {
  //Extract contour
  var contour = getContour(pixels, simplify)

  //Apply warp to positions
  var positions = transformPositions(contour.positions, options, size)
  var edges     = contour.edges
  var flip = "ccw" === options.orientation

  //Clean up the PSLG, resolve self intersections, etc.
  cleanPSLG(positions, edges)

  //If triangulate flag passed, triangulate the result
  if(options.polygons || options.polygon || options.polyline) {
    var result = toPolygonCrappy(edges, positions)
    var nresult = new Array(result.length)
    for(var i=0; i<result.length; ++i) {
      var loops = result[i]
      var nloops = new Array(loops.length)
      for(var j=0; j<loops.length; ++j) {
        var loop = loops[j]
        var nloop = new Array(loop.length)
        for(var k=0; k<loop.length; ++k) {
          nloop[k] = positions[loop[k]].slice()
        }
        if(flip) {
          nloop.reverse()
        }
        nloops[j] = nloop
      }
      nresult[i] = nloops
    }
    return nresult
  } else if(options.triangles || options.triangulate || options.triangle) {
    return {
      cells: cdt2d(positions, edges, {
        delaunay: false,
        exterior: false,
        interior: true
      }),
      positions: positions
    }
  } else {
    return {
      edges:     edges,
      positions: positions
    }
  }
}

function processPixels(pixels, options, size) {
  try {
    return processPixelsImpl(pixels, options, size, true)
  } catch(e) {}
  try {
    return processPixelsImpl(pixels, options, size, false)
  } catch(e) {}
  if(options.polygons || options.polyline || options.polygon) {
    return []
  }
  if(options.triangles || options.triangulate || options.triangle) {
    return {
      cells: [],
      positions: []
    }
  }
  return {
    edges: [],
    positions: []
  }
}

function vectorizeText(str, canvas, context, options) {
  var size = options.size || 64
  var lineSpacing = options.lineSpacing || 1.25

  context.font = [
    options.fontStyle,
    options.fontVariant,
    options.fontWeight,
    size + "px",
    options.font
  ].filter(function(d) {return d}).join(" ")
  context.textAlign = "start"
  context.textBaseline = "alphabetic"
  context.direction = "ltr"

  var pixels = getPixels(canvas, context, str, size, lineSpacing)

  return processPixels(pixels, options, size)
}
<commit_msg>adjust sub & sup positions and size<commit_after>"use strict"

module.exports = vectorizeText
module.exports.processPixels = processPixels

var surfaceNets = require('surface-nets')
var ndarray = require('ndarray')
var simplify = require('simplify-planar-graph')
var cleanPSLG = require('clean-pslg')
var cdt2d = require('cdt2d')
var toPolygonCrappy = require('planar-graph-to-polyline')

var TAG_bold = "b"
var CHR_bold = 'b'

var TAG_italic = "i"
var CHR_italic = 'i'

var TAG_super = "sup"
var CHR_super = 'P'

var TAG_sub = "sub"
var CHR_sub = 'B'

function parseTag(tag, TAG_CHR, str, map) {

  var opnTag =  "<"  + tag + ">"
  var clsTag =  "</" + tag + ">"

  var nOPN = opnTag.length
  var nCLS = clsTag.length

  var nStr = str.length

  var a = 0
  var b = -nCLS
  while (a > -1) {
    a = str.indexOf(opnTag, b + nCLS)
    if(a === -1) break

    b = str.indexOf(clsTag, a + nOPN)
    if(b === -1) break

    if(b <= a) break

    for(var i = a; i < b + nCLS; ++i){
      if((i < a + nOPN) || (i >= b)) {
        map[i] = null
      } else {
        if(map[i] !== null) {
          if(map[i].indexOf(TAG_CHR) === -1) map[i] += TAG_CHR
        }
      }
    }
  }

  return map
}

function transformPositions(positions, options, size) {
  var align = options.textAlign || "start"
  var baseline = options.textBaseline || "alphabetic"

  var lo = [1<<30, 1<<30]
  var hi = [0,0]
  var n = positions.length
  for(var i=0; i<n; ++i) {
    var p = positions[i]
    for(var j=0; j<2; ++j) {
      lo[j] = Math.min(lo[j], p[j])|0
      hi[j] = Math.max(hi[j], p[j])|0
    }
  }

  var xShift = 0
  switch(align) {
    case "center":
      xShift = -0.5 * (lo[0] + hi[0])
    break

    case "right":
    case "end":
      xShift = -hi[0]
    break

    case "left":
    case "start":
      xShift = -lo[0]
    break

    default:
      throw new Error("vectorize-text: Unrecognized textAlign: '" + align + "'")
  }

  var yShift = 0
  switch(baseline) {
    case "hanging":
    case "top":
      yShift = -lo[1]
    break

    case "middle":
      yShift = -0.5 * (lo[1] + hi[1])
    break

    case "alphabetic":
    case "ideographic":
      yShift = -3 * size
    break

    case "bottom":
      yShift = -hi[1]
    break

    default:
      throw new Error("vectorize-text: Unrecoginized textBaseline: '" + baseline + "'")
  }

  var scale = 1.0 / size
  if("lineHeight" in options) {
    scale *= +options.lineHeight
  } else if("width" in options) {
    scale = options.width / (hi[0] - lo[0])
  } else if("height" in options) {
    scale = options.height / (hi[1] - lo[1])
  }

  return positions.map(function(p) {
    return [ scale * (p[0] + xShift), scale * (p[1] + yShift) ]
  })
}

function getPixels(canvas, context, raw_str, fontSize, lineSpacing) {

  raw_str = raw_str.replace(/\<br\>/g, '\n')

  var activeStyle = ""
  var map = []
  for (j = 0; j < raw_str.length; ++j) {
    map[j] = activeStyle
  }
  map = parseTag(TAG_bold,
                 CHR_bold, raw_str, map)
  map = parseTag(TAG_italic,
                 CHR_italic, raw_str, map)
  map = parseTag(TAG_super,
                 CHR_super, raw_str, map)
  map = parseTag(TAG_sub,
                 CHR_sub, raw_str, map)

  var allStyles = []
  var plainText = ""
  for(j = 0; j < raw_str.length; ++j) {
    if(map[j] !== null) {
      plainText += raw_str[j]
      allStyles.push(map[j])
    }
  }

  var allTexts = plainText.split('\n')

  var numberOfLines = allTexts.length
  var lineHeight = Math.round(lineSpacing * fontSize)
  var offsetX = fontSize
  var offsetY = fontSize * 2
  var maxWidth = 0
  var minHeight = numberOfLines * lineHeight + offsetY

  if(canvas.height < minHeight) {
    canvas.height = minHeight
  }

  context.fillStyle = "#000"
  context.fillRect(0, 0, canvas.width, canvas.height)

  context.fillStyle = "#fff"
  var i, j, xPos, yPos, zPos
  var nDone = 0

  for(i = 0; i < numberOfLines; ++i) {

    var txt = allTexts[i] + '\n'
    xPos = 0
    yPos = i * lineHeight
    zPos = fontSize

    var buffer = ""
    function writeBuffer() {
      if(buffer !== "") {
        var delta = context.measureText(buffer).width

        context.fillText(buffer, offsetX + xPos, offsetY + yPos)
        xPos += delta
      }
    }

    function changeStyle(oldStyle, newStyle) {

      function getTextFontSize() {
        return "" + Math.round(zPos) + "px ";
      }

      var ctxFont = "" + context.font;

      var wasSub = (oldStyle.indexOf(CHR_sub) > -1)
      var is_Sub = (newStyle.indexOf(CHR_sub) > -1)

      if(!wasSub && is_Sub) {
        ctxFont = ctxFont.replace(getTextFontSize(), "?px ")
        zPos *= 0.75
        ctxFont = ctxFont.replace("?px ", getTextFontSize())
        yPos += 0.25 * lineHeight
      }
      if(wasSub && !is_Sub) {
        ctxFont = ctxFont.replace(getTextFontSize(), "?px ")
        zPos /= 0.75
        ctxFont = ctxFont.replace("?px ", getTextFontSize())
        yPos -= 0.25 * lineHeight
      }

      var wasSuper = (oldStyle.indexOf(CHR_super) > -1)
      var is_Super = (newStyle.indexOf(CHR_super) > -1)

      if(!wasSuper && is_Super) {
        ctxFont = ctxFont.replace(getTextFontSize(), "?px ")
        zPos *= 0.75
        ctxFont = ctxFont.replace("?px ", getTextFontSize())
        yPos -= 0.25 * lineHeight
      }
      if(wasSuper && !is_Super) {
        ctxFont = ctxFont.replace(getTextFontSize(), "?px ")
        zPos /= 0.75
        ctxFont = ctxFont.replace("?px ", getTextFontSize())
        yPos += 0.25 * lineHeight
      }

      var wasBold = (oldStyle.indexOf(CHR_bold) > -1)
      var is_Bold = (newStyle.indexOf(CHR_bold) > -1)

      if(!wasBold && is_Bold) {
        if(wasItalic) {
          ctxFont = ctxFont.replace("italic ", "italic bold ")
        } else {
          ctxFont = "bold " + ctxFont
        }
      }
      if(wasBold && !is_Bold) {
        ctxFont = ctxFont.replace("bold ", '')
      }

      var wasItalic = (oldStyle.indexOf(CHR_italic) > -1)
      var is_Italic = (newStyle.indexOf(CHR_italic) > -1)

      if(!wasItalic && is_Italic) {
        ctxFont = "italic " + ctxFont
      }
      if(wasItalic && !is_Italic) {
        ctxFont = ctxFont.replace("italic ", '')
      }

      context.font = ctxFont
    }

    for(j = 0; j < txt.length; ++j) {
      var style = (j + nDone < allStyles.length) ? allStyles[j + nDone] : allStyles[allStyles.length - 1]
      if(activeStyle === style) {
        buffer += txt[j]
      } else {
        writeBuffer()
        buffer = txt[j]

        changeStyle(activeStyle, style)
        activeStyle = style
      }
    }
    writeBuffer()

    nDone += txt.length

    var width = Math.round(xPos + 2 * offsetX) | 0
    if(maxWidth < width) maxWidth = width
  }

  //Cut pixels from image
  var xCut = maxWidth
  var yCut = offsetY + lineHeight * numberOfLines
  var pixels = ndarray(context.getImageData(0, 0, xCut, yCut).data, [yCut, xCut, 4])
  return pixels.pick(-1, -1, 0).transpose(1, 0)
}

function getContour(pixels, doSimplify) {
  var contour = surfaceNets(pixels, 128)
  if(doSimplify) {
    return simplify(contour.cells, contour.positions, 0.25)
  }
  return {
    edges: contour.cells,
    positions: contour.positions
  }
}

function processPixelsImpl(pixels, options, size, simplify) {
  //Extract contour
  var contour = getContour(pixels, simplify)

  //Apply warp to positions
  var positions = transformPositions(contour.positions, options, size)
  var edges     = contour.edges
  var flip = "ccw" === options.orientation

  //Clean up the PSLG, resolve self intersections, etc.
  cleanPSLG(positions, edges)

  //If triangulate flag passed, triangulate the result
  if(options.polygons || options.polygon || options.polyline) {
    var result = toPolygonCrappy(edges, positions)
    var nresult = new Array(result.length)
    for(var i=0; i<result.length; ++i) {
      var loops = result[i]
      var nloops = new Array(loops.length)
      for(var j=0; j<loops.length; ++j) {
        var loop = loops[j]
        var nloop = new Array(loop.length)
        for(var k=0; k<loop.length; ++k) {
          nloop[k] = positions[loop[k]].slice()
        }
        if(flip) {
          nloop.reverse()
        }
        nloops[j] = nloop
      }
      nresult[i] = nloops
    }
    return nresult
  } else if(options.triangles || options.triangulate || options.triangle) {
    return {
      cells: cdt2d(positions, edges, {
        delaunay: false,
        exterior: false,
        interior: true
      }),
      positions: positions
    }
  } else {
    return {
      edges:     edges,
      positions: positions
    }
  }
}

function processPixels(pixels, options, size) {
  try {
    return processPixelsImpl(pixels, options, size, true)
  } catch(e) {}
  try {
    return processPixelsImpl(pixels, options, size, false)
  } catch(e) {}
  if(options.polygons || options.polyline || options.polygon) {
    return []
  }
  if(options.triangles || options.triangulate || options.triangle) {
    return {
      cells: [],
      positions: []
    }
  }
  return {
    edges: [],
    positions: []
  }
}

function vectorizeText(str, canvas, context, options) {
  var size = options.size || 64
  var lineSpacing = options.lineSpacing || 1.25

  context.font = [
    options.fontStyle,
    options.fontVariant,
    options.fontWeight,
    size + "px",
    options.font
  ].filter(function(d) {return d}).join(" ")
  context.textAlign = "start"
  context.textBaseline = "alphabetic"
  context.direction = "ltr"

  var pixels = getPixels(canvas, context, str, size, lineSpacing)

  return processPixels(pixels, options, size)
}
<|endoftext|>


<commit_before>var unordered = require('unordered-set')
var util = require('util')
var events = require('events')

module.exports = watch

function watch (db, key, cb) {
  var w = new Watcher(db, key)
  w._index = db._watching.push(w) - 1
  w.start(cb)
  return w
}

function Watcher (db, key) {
  events.EventEmitter.call(this)

  this.key = key

  this._index = 0
  this._db = db
  this._kicked = 0
  this._nodes = null
  this._destroyed = false
  this._onkick = onkick.bind(this)
}

util.inherits(Watcher, events.EventEmitter)

Watcher.prototype.destroy = function (err) {
  if (this._destroyed) return
  this._destroyed = true

  unordered.remove(this._db._watching, this)
  if (err) this.emit('error', err)
  this.emit('close')
}

Watcher.prototype.start = function (onchange) {
  if (onchange) this.on('change', onchange)
  this._kick()
}

Watcher.prototype._kick = function () {
  this._kicked++
  this._db.get(this.key, {prefix: true, map: false, reduce: false}, this._onkick)
}

function same (a, b) {
  if (a.length !== b.length) return false

  for (var i = 0; i < a.length; i++) {
    if (a[i].feed !== b[i].feed || a[i].seq !== b[i].seq) return false
  }

  return true
}

function sortByFeed (a, b) {
  return a.feed - b.feed
}

function onkick (err, nodes) {
  if (err) return this._destroy(err)

  var kicked = this._kicked

  this._kicked = 0
  nodes = nodes.sort(sortByFeed)

  if (!this._nodes) {
    this._nodes = nodes
    this.emit('watching')
  }

  if (!same(nodes, this._nodes)) {
    this._nodes = nodes
    this.emit('change')
    return
  }

  // there is a chance the db has been updated while we
  // ran the query - retry
  if (kicked > 1) this._kick()
}
<commit_msg>Rename call to non-existent ._destroy() method to .destroy() (#114)<commit_after>var unordered = require('unordered-set')
var util = require('util')
var events = require('events')

module.exports = watch

function watch (db, key, cb) {
  var w = new Watcher(db, key)
  w._index = db._watching.push(w) - 1
  w.start(cb)
  return w
}

function Watcher (db, key) {
  events.EventEmitter.call(this)

  this.key = key

  this._index = 0
  this._db = db
  this._kicked = 0
  this._nodes = null
  this._destroyed = false
  this._onkick = onkick.bind(this)
}

util.inherits(Watcher, events.EventEmitter)

Watcher.prototype.destroy = function (err) {
  if (this._destroyed) return
  this._destroyed = true

  unordered.remove(this._db._watching, this)
  if (err) this.emit('error', err)
  this.emit('close')
}

Watcher.prototype.start = function (onchange) {
  if (onchange) this.on('change', onchange)
  this._kick()
}

Watcher.prototype._kick = function () {
  this._kicked++
  this._db.get(this.key, {prefix: true, map: false, reduce: false}, this._onkick)
}

function same (a, b) {
  if (a.length !== b.length) return false

  for (var i = 0; i < a.length; i++) {
    if (a[i].feed !== b[i].feed || a[i].seq !== b[i].seq) return false
  }

  return true
}

function sortByFeed (a, b) {
  return a.feed - b.feed
}

function onkick (err, nodes) {
  if (err) return this.destroy(err)

  var kicked = this._kicked

  this._kicked = 0
  nodes = nodes.sort(sortByFeed)

  if (!this._nodes) {
    this._nodes = nodes
    this.emit('watching')
  }

  if (!same(nodes, this._nodes)) {
    this._nodes = nodes
    this.emit('change')
    return
  }

  // there is a chance the db has been updated while we
  // ran the query - retry
  if (kicked > 1) this._kick()
}
<|endoftext|>


<commit_before>
/**
 * @module subscriber
 * @requires module:utils~Formatter module:storage
 */

(/** @lends module:subscriber */function(){

var uuid   = require('node-uuid');
var bcrypt = require('bcrypt');

var fmt = require('../utils/formatter');
var msg = require('./msg');
var stg = require('./storage');

// Default session timeout in minutes
var defTimeout = 180;

/**
 * A controller containins the primary business logic for all subuscriber module
 * services. It is constructed with references to necessary external services
 * and provides an interface for each of its public service offerings.
 *
 * @constructor
 * @param {Object} context          - wrapper of necessary services
 * @param {Object} context.database - database engine
 * @param {Object} context.mailer   - SMTP engine
 * @param {Object} context.template - template engine
 * @param {Object} context.logger   - logger engine
 */

function Controller(s, m, t, h, l) {
  this.storage  = s;
  this.mailer   = m;
  this.template = t;
  this.server   = h;
  this.logger   = l;
}
exports.Controller = Controller;

Controller.prototype.toFormatter = function(f) {
  f.begin('Controller');
  this.storage.toFormatter(f);
  this.mailer.toFormatter(f);
  this.template.toFormatter(f);
  f.end();
};

Controller.prototype.toString = fmt.toString;

/**
 * Determine if the provided token belongs to a valid active session. If
 * the session token is valid subscriber and session identifier.
 *
 * @param {String} token - session token
 * @param {Function} callback - a standard callback function
 */
Controller.prototype.authorize = function(token, callback) {
  console.log('authorizing: ' + token);
  this.storage.getSession(token, function(err, succ) {
    if(err) {
      callback(err);
    } else {
    callback(null, { subscriber_id: succ.subscriber_id, session_id: succ.id });
    }
  });
};

/**
 * Given an email and password attempt to login the subscriber. This involves
 * first validating the email/password, second creating a new session for the
 * subscriber, and third returning the session token needed for further
 * authenticatd requests.
 *
 * @param {String} email - email address to login
 * @param {String} pwd - password to authenticate the email
 * @param {Function} callback - standard callback
 */
Controller.prototype.login = function(email, pwd, callback) {
  var that = this;
  this.storage.getSubscriberByEmail(email, function(err, succ) {
    var token, expireTime;
    if(err) {
      that.logger.error(err);
      callback(err);
    } else {
      if(bcrypt.compareSync(pwd, succ.password)) {
        token = uuid.v4();
        expireTime = new Date((new Date()).getTime() + defTimeout * 60000);
        that.storage.createSession(token, succ.id, expireTime.toISOString(),
          function(_err, session) {
            if(_err){
              that.logger.error(_err);
              callback(_err);
            }
            callback(null, session.key);
        });
      } else {
        callback(msg.invalidPassword());
      }
    }
  });
};

Controller.prototype.logout = function(sessionID, callback) {
  // if a valid session then delete the session
  console.log('logout: ' + sessionID);
  this.storage.deleteSession(sessionID, callback);
};

Controller.prototype.register = function(email, pwd, srcIp, callback) {
  var current, token, hash, that;
  current = new Date();
  token = uuid.v4();
  hash = bcrypt.hashSync(pwd, 10);
  that = this;
  // Create the subscriber entry and send the verification email
  this.storage.createSubscriber(email, hash, current.toISOString(), srcIp,
                                token, function(err, succ) {
    var subject, body;
    if(err) {
      that.logger.error(err);
      callback(err);
    } else {
      subject = 'Flowsim Verify Email Address';
      body = that.template.render('verification', {
        baseUrl: that.server.baseUrl(),
        token: token
      });
      that.mailer.send(email, subject, body);
      callback(null, msg.success());
    }
  });
};

Controller.prototype.verify = function(token, callback) {
  // if the verification token is valid update
  // subscriber state
  // otherwise send an error
  var that = this;
  this.storage.verifySubscriber(token, function(err, result){
    if(err){
      callback(err);
      that.logger.error(err);
    } else {
      callback(null, result);
    }
  });
};

Controller.prototype.forgot = function(email, callback) {
  // update the subscriber state and send and email
  // or send an error
  var token = uuid.v4();
  var that = this;
  this.storage.resetSubscriber(email, token, function(err, succ) {
    var body, subject;
    if(err) {
      that.logger.error(err);
      callback(err);
    } else {
      body = that.template.render('reset', {
        baseUrl: that.server.baseUrl(),
        token: token
      });
      that.mailer.send(email, subject, body);
      callback(null, result);
    }
  });
};

Controller.prototype.reset = function(token, password, callback) {
  var hash = bcrypt.hashSync(password, 10);
  var that = this;
  this.storage.updateSubscriberPasswordByToken(token, hash,
    function(err, result){
      if(err){
        that.logger.error(err);
        callback(err);
      } else {
        callback(null, result);
      }
  });
};

Controller.prototype.update = function(subscriber_id, session_id, oldPwd,
                                       newPwd, callback) {
  var that = this;
  console.log('update: ' + subscriber_id);
  this.storage.getSubscriberById(subscriber_id, function(err, succ) {
    var hash;
    if(err) {
      callback(err);
    } else {
      if(bcrypt.compareSync(oldPwd, succ.password)) {
        hash = bcrypt.hashSync(newPwd, 10);
        that.storage.updateSubscriberPassword(subscriber_id, hash, callback);
      } else {
        callback(msg.invalidPassword());
      }
    }
  });
};

})();

<commit_msg>sub controller doc<commit_after>
/**
 * @module subscriber
 * @requires module:utils~Formatter module:storage
 */

(/** @lends module:subscriber */function(){

var uuid   = require('node-uuid');
var bcrypt = require('bcrypt');

var fmt = require('../utils/formatter');
var msg = require('./msg');
var stg = require('./storage');

// Default session timeout in minutes
var defTimeout = 180;

/**
 * A controller containins the primary business logic for all subuscriber module
 * services. It is constructed with references to necessary external services
 * and provides an interface for each of its public service offerings.
 *
 * @constructor
 * @param {Object} context          - wrapper of necessary services
 * @param {Object} context.database - database engine
 * @param {Object} context.mailer   - SMTP engine
 * @param {Object} context.template - template engine
 * @param {Object} context.logger   - logger engine
 */

function Controller(s, m, t, h, l) {
  this.storage  = s;
  this.mailer   = m;
  this.template = t;
  this.server   = h;
  this.logger   = l;
}
exports.Controller = Controller;

Controller.prototype.toFormatter = function(f) {
  f.begin('Controller');
  this.storage.toFormatter(f);
  this.mailer.toFormatter(f);
  this.template.toFormatter(f);
  f.end();
};

Controller.prototype.toString = fmt.toString;

/**
 * Determine if the provided token belongs to a valid active session. If
 * the session token is valid subscriber and session identifier.
 *
 * @param {String} token - session token
 * @param {Function} callback - a standard callback function
 */
Controller.prototype.authorize = function(token, callback) {
  console.log('authorizing: ' + token);
  this.storage.getSession(token, function(err, succ) {
    if(err) {
      callback(err);
    } else {
    callback(null, { subscriber_id: succ.subscriber_id, session_id: succ.id });
    }
  });
};

/**
 * Given an email and password attempt to login the subscriber. This involves
 * first validating the email/password, second creating a new session for the
 * subscriber, and third returning the session token needed for further
 * authenticatd requests.
 *
 * @param {String} email - email address to login
 * @param {String} pwd - password to authenticate the email
 * @param {Function} callback - standard callback
 */
Controller.prototype.login = function(email, pwd, callback) {
  var that = this;
  this.storage.getSubscriberByEmail(email, function(err, succ) {
    var token, expireTime;
    if(err) {
      that.logger.error(err);
      callback(err);
    } else {
      if(bcrypt.compareSync(pwd, succ.password)) {
        token = uuid.v4();
        expireTime = new Date((new Date()).getTime() + defTimeout * 60000);
        that.storage.createSession(token, succ.id, expireTime.toISOString(),
          function(_err, session) {
            if(_err){
              that.logger.error(_err);
              callback(_err);
            }
            callback(null, session.key);
        });
      } else {
        callback(msg.invalidPassword());
      }
    }
  });
};

/**
 * Given a session id attempt to remove session entry from database.
 *
 * @param {Integer} sessionID - session entry id
 * @param {Function} callback - standard callback
 */
Controller.prototype.logout = function(sessionID, callback) {
  // if a valid session then delete the session
  console.log('logout: ' + sessionID);
  this.storage.deleteSession(sessionID, callback);
};

Controller.prototype.register = function(email, pwd, srcIp, callback) {
  var current, token, hash, that;
  current = new Date();
  token = uuid.v4();
  hash = bcrypt.hashSync(pwd, 10);
  that = this;
  // Create the subscriber entry and send the verification email
  this.storage.createSubscriber(email, hash, current.toISOString(), srcIp,
                                token, function(err, succ) {
    var subject, body;
    if(err) {
      that.logger.error(err);
      callback(err);
    } else {
      subject = 'Flowsim Verify Email Address';
      body = that.template.render('verification', {
        baseUrl: that.server.baseUrl(),
        token: token
      });
      that.mailer.send(email, subject, body);
      callback(null, msg.success());
    }
  });
};

/**
 * Given a token attempt the verify the subscriber associated with it.
 *
 * @param {String} token - verification token
 * @param {Function} callback - standard callback
 */
Controller.prototype.verify = function(token, callback) {
  // if the verification token is valid update
  // subscriber state
  // otherwise send an error
  var that = this;
  this.storage.verifySubscriber(token, function(err, result){
    if(err){
      callback(err);
      that.logger.error(err);
    } else {
      callback(null, result);
    }
  });
};

/**
 * Given an email attempt to reset subscriber password. A reset token is
 * generated and sent in an email to subscriber.
 *
 * @param {String} email - subscriber email address
 * @param {Function} callback - standard callback
 */
Controller.prototype.forgot = function(email, callback) {
  // update the subscriber state and send and email
  // or send an error
  var token = uuid.v4();
  var that = this;
  this.storage.resetSubscriber(email, token, function(err, succ) {
    var body, subject;
    if(err) {
      that.logger.error(err);
      callback(err);
    } else {
      body = that.template.render('reset', {
        baseUrl: that.server.baseUrl(),
        token: token
      });
      that.mailer.send(email, subject, body);
      callback(null, result);
    }
  });
};

/**
 * Given a reset token and password attempt to update subscriber password.
 * The subscriber state is moved to 'ACTIVE'
 *
 * @param {String} email - subscriber email address
 * @param {Function} callback - standard callback
 */
Controller.prototype.reset = function(token, password, callback) {
  var hash = bcrypt.hashSync(password, 10);
  var that = this;
  this.storage.updateSubscriberPasswordByToken(token, hash,
    function(err, result){
      if(err){
        that.logger.error(err);
        callback(err);
      } else {
        callback(null, result);
      }
  });
};

Controller.prototype.update = function(subscriber_id, session_id, oldPwd,
                                       newPwd, callback) {
  var that = this;
  console.log('update: ' + subscriber_id);
  this.storage.getSubscriberById(subscriber_id, function(err, succ) {
    var hash;
    if(err) {
      callback(err);
    } else {
      if(bcrypt.compareSync(oldPwd, succ.password)) {
        hash = bcrypt.hashSync(newPwd, 10);
        that.storage.updateSubscriberPassword(subscriber_id, hash, callback);
      } else {
        callback(msg.invalidPassword());
      }
    }
  });
};

})();
<|endoftext|>