Skip to content
Browse files

hello, gdata

  • Loading branch information...
0 parents commit 6d16a708e5eaf258c9f066a0418f3857acbc7046 @smurthas committed Jun 9, 2011
Showing with 525 additions and 0 deletions.
  1. +4 −0 .gitignore
  2. +23 −0 README.md
  3. +72 −0 gdata.js
  4. +359 −0 node_modules/request/main.js
  5. +14 −0 node_modules/request/package.json
  6. +11 −0 package.json
  7. +42 −0 test.js
4 .gitignore
@@ -0,0 +1,4 @@
+.DS_Store
+node_modules/.bin
+node_modules/express
+node_modules/connect
23 README.md
@@ -0,0 +1,23 @@
+# gdata-js
+
+Simple Google Data API client for OAuth 2.0 with express + connect.
+
+ npm install gdata-js
+
+## Usage
+
+gdata-js has two methods:
+
+* getAccessToken(_req_, _res_, _callback_): Goes through the OAuth 2.0 flow to get an access token
+* getFeed(_http_method_, _path_, _params_, _callback_): Does a call to the Google Data API to get a feed object.
+
+oauth_token must be contained in the _params_ argument as demonstrated in test.js
+
+## Test
+
+Enter your consumer key and secret in test.js
+
+ cd test
+ node test.js
+
+open http://localhost:8553
72 gdata.js
@@ -0,0 +1,72 @@
+var querystring = require('querystring');
+var request = require('request');
+var https = require('https');
+
+var oauthBase = 'https://accounts.google.com/o/oauth2';
+
+module.exports = function(client_id, client_secret, redirect_uri) {
+ var clientID = client_id;
+ var clientSecret = client_secret;
+ var redirectURI = redirect_uri;
+ this.getAccessToken = function(scope, req, res, callback) {
+ if(req.query.error) {
+ callback(req.query.error);
+ } else if(!req.query.code) {
+ res.redirect(oauthBase + '/auth?' + querystring.stringify({client_id: clientID ,
+ redirect_uri: redirectURI,
+ scope: scope,
+ response_type: 'code'}));
+ } else {
+ doPost({grant_type:'authorization_code',
+ code:req.query.code,
+ client_id:clientID,
+ client_secret:clientSecret,
+ redirect_uri:redirectURI}, function(err, tkn) {
+ callback(err, tkn);
+ });
+ }
+ }
+
+ this.getFeed = function(url, params, callback) {
+ if(!callback && typeof params === 'function') {
+ callback = params;
+ params = {};
+ }
+ params.alt = 'json';
+ var reqUrl = url + '?' + querystring.stringify(params);
+ request.get({uri:reqUrl}, function(err, resp, body) {
+ if(!err && body) {
+ try {
+ body = JSON.parse(body);
+ } catch(e) {
+ callback(e, body);
+ return;
+ }
+ callback(null, body);
+ } else {
+ callback(err, body);
+ }
+ });
+ }
+ return this;
+}
+
+function doPost(body, callback) {
+ var options = {
+ host: 'accounts.google.com',
+ port: 443,
+ path: '/o/oauth2/token',
+ method: 'POST',
+ headers: {'Content-Type':'application/x-www-form-urlencoded'}
+ };
+ var httpsReq = https.request(options, function(httpsRes) {
+ httpsRes.on('data', function(data) {
+ callback(null, JSON.parse(data));
+ });
+ });
+ httpsReq.write(querystring.stringify(body));
+ httpsReq.on('error', function(e) {
+ callback(e, null);
+ });
+ httpsReq.end();
+}
359 node_modules/request/main.js
@@ -0,0 +1,359 @@
+// Copyright 2010-2011 Mikeal Rogers
+//
+// 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 http = require('http')
+ , https = false
+ , tls = false
+ , url = require('url')
+ , util = require('util')
+ , stream = require('stream')
+ , qs = require('querystring')
+ ;
+
+try {
+ https = require('https');
+} catch (e) {}
+
+try {
+ tls = require('tls');
+} catch (e) {}
+
+var toBase64 = function(str) {
+ return (new Buffer(str || "", "ascii")).toString("base64");
+};
+
+// Hacky fix for pre-0.4.4 https
+if (https && !https.Agent) {
+ https.Agent = function (options) {
+ http.Agent.call(this, options);
+ }
+ util.inherits(https.Agent, http.Agent);
+
+ https.Agent.prototype._getConnection = function(host, port, cb) {
+ var s = tls.connect(port, host, this.options, function() {
+ // do other checks here?
+ if (cb) cb();
+ });
+
+ return s;
+ };
+}
+
+var isUrl = /^https?:/;
+
+var globalPool = {};
+
+var Request = function (options) {
+ stream.Stream.call(this);
+ this.readable = true;
+ this.writable = true;
+
+ for (i in options) {
+ this[i] = options[i];
+ }
+ if (!this.pool) this.pool = globalPool;
+ this.dests = [];
+}
+util.inherits(Request, stream.Stream);
+Request.prototype.getAgent = function (host, port) {
+ if (!this.pool[host+':'+port]) {
+ this.pool[host+':'+port] = new this.httpModule.Agent({host:host, port:port});
+ }
+ return this.pool[host+':'+port];
+}
+Request.prototype.request = function () {
+ var options = this;
+ if (options.url) {
+ // People use this property instead all the time so why not just support it.
+ options.uri = options.url;
+ delete options.url;
+ }
+
+ if (!options.uri) {
+ throw new Error("options.uri is a required argument");
+ } else {
+ if (typeof options.uri == "string") options.uri = url.parse(options.uri);
+ }
+ if (options.proxy) {
+ if (typeof options.proxy == 'string') options.proxy = url.parse(options.proxy);
+ }
+
+ options._redirectsFollowed = options._redirectsFollowed || 0;
+ options.maxRedirects = (options.maxRedirects !== undefined) ? options.maxRedirects : 10;
+ options.followRedirect = (options.followRedirect !== undefined) ? options.followRedirect : true;
+
+ options.method = options.method || 'GET';
+ options.headers = options.headers || {};
+
+ var setHost = false;
+ if (!options.headers.host) {
+ options.headers.host = options.uri.hostname;
+ if (options.uri.port) {
+ if ( !(options.uri.port === 80 && options.uri.protocol === 'http:') &&
+ !(options.uri.port === 443 && options.uri.protocol === 'https:') )
+ options.headers.host += (':'+options.uri.port);
+ }
+ setHost = true;
+ }
+
+ if (!options.uri.pathname) {options.uri.pathname = '/';}
+ if (!options.uri.port) {
+ if (options.uri.protocol == 'http:') {options.uri.port = 80;}
+ else if (options.uri.protocol == 'https:') {options.uri.port = 443;}
+ }
+
+ if (options.bodyStream || options.responseBodyStream) {
+ console.error('options.bodyStream and options.responseBodyStream is deprecated. You should now send the request object to stream.pipe()');
+ this.pipe(options.responseBodyStream || options.bodyStream)
+ }
+
+ if (options.proxy) {
+ options.port = options.proxy.port;
+ options.host = options.proxy.hostname;
+ } else {
+ options.port = options.uri.port;
+ options.host = options.uri.hostname;
+ }
+
+ if (options.onResponse === true) {
+ options.onResponse = options.callback;
+ delete options.callback;
+ }
+
+ var clientErrorHandler = function (error) {
+ if (setHost) delete options.headers.host;
+ options.emit('error', error);
+ };
+ if (options.onResponse) options.on('error', function (e) {options.onResponse(e)});
+ if (options.callback) options.on('error', function (e) {options.callback(e)});
+
+
+ if (options.uri.auth && !options.headers.authorization) {
+ options.headers.authorization = "Basic " + toBase64(options.uri.auth.split(':').map(qs.unescape).join(':'));
+ }
+ if (options.proxy && options.proxy.auth && !options.headers['proxy-authorization']) {
+ options.headers['proxy-authorization'] = "Basic " + toBase64(options.proxy.auth.split(':').map(qs.unescape).join(':'));
+ }
+
+ options.path = options.uri.href.replace(options.uri.protocol + '//' + options.uri.host, '');
+ if (options.path.length === 0) options.path = '/';
+
+ if (options.proxy) options.path = (options.uri.protocol + '//' + options.uri.host + options.path);
+
+ if (options.json) {
+ options.headers['content-type'] = 'application/json';
+ options.body = JSON.stringify(options.json);
+ } else if (options.multipart) {
+ options.body = '';
+ options.headers['content-type'] = 'multipart/related;boundary="frontier"';
+
+ if (!options.multipart.forEach) throw new Error('Argument error, options.multipart.');
+ options.multipart.forEach(function (part) {
+ var body = part.body;
+ if(!body) throw Error('Body attribute missing in multipart.');
+ delete part.body;
+ options.body += '--frontier\r\n';
+ Object.keys(part).forEach(function(key){
+ options.body += key + ': ' + part[key] + '\r\n'
+ })
+ options.body += '\r\n' + body + '\r\n';
+ })
+ options.body += '--frontier--'
+ }
+
+ if (options.body) {
+ if (!Buffer.isBuffer(options.body)) {
+ options.body = new Buffer(options.body);
+ }
+ if (options.body.length) {
+ options.headers['content-length'] = options.body.length;
+ } else {
+ throw new Error('Argument error, options.body.');
+ }
+ }
+
+ options.httpModule =
+ {"http:":http, "https:":https}[options.proxy ? options.proxy.protocol : options.uri.protocol]
+
+ if (!options.httpModule) throw new Error("Invalid protocol");
+
+ if (options.pool === false) {
+ options.agent = false;
+ } else {
+ if (options.maxSockets) {
+ options.agent = options.getAgent(options.host, options.port);
+ options.agent.maxSockets = options.maxSockets;
+ }
+ if (options.pool.maxSockets) {
+ options.agent = options.getAgent(options.host, options.port);
+ options.agent.maxSockets = options.pool.maxSockets;
+ }
+ }
+
+ options.req = options.httpModule.request(options, function (response) {
+ options.response = response;
+ if (setHost) delete options.headers.host;
+
+ if (response.statusCode >= 300 &&
+ response.statusCode < 400 &&
+ options.followRedirect &&
+ options.method !== 'PUT' &&
+ options.method !== 'POST' &&
+ response.headers.location) {
+ if (options._redirectsFollowed >= options.maxRedirects) {
+ options.emit('error', new Error("Exceeded maxRedirects. Probably stuck in a redirect loop."));
+ }
+ options._redirectsFollowed += 1;
+ if (!isUrl.test(response.headers.location)) {
+ response.headers.location = url.resolve(options.uri.href, response.headers.location);
+ }
+ options.uri = response.headers.location;
+ delete options.req;
+ delete options.agent;
+ if (options.headers) {
+ delete options.headers.host;
+ }
+ request(options, options.callback);
+ return; // Ignore the rest of the response
+ } else {
+ options._redirectsFollowed = 0;
+ // Be a good stream and emit end when the response is finished.
+ // Hack to emit end on close becuase of a core bug that never fires end
+ response.on('close', function () {options.response.emit('end')})
+
+ if (options.encoding) {
+ if (options.dests.length !== 0) {
+ console.error("Ingoring encoding parameter as this stream is being piped to another stream which makes the encoding option invalid.");
+ } else {
+ response.setEncoding(options.encoding);
+ }
+ }
+
+ if (options.dests.length !== 0) {
+ options.dests.forEach(function (dest) {
+ response.pipe(dest);
+ })
+ if (options.onResponse) options.onResponse(null, response);
+ if (options.callback) options.callback(null, response, options.responseBodyStream);
+
+ } else {
+ if (options.onResponse) {
+ options.onResponse(null, response);
+ }
+ if (options.callback) {
+ var buffer = '';
+ response.on("data", function (chunk) {
+ buffer += chunk;
+ })
+ response.on("end", function () {
+ options.callback(null, response, buffer);
+ })
+ ;
+ }
+ }
+ }
+ })
+
+ options.req.on('error', clientErrorHandler);
+
+ options.once('pipe', function (src) {
+ if (options.ntick) throw new Error("You cannot pipe to this stream after the first nextTick() after creation of the request stream.")
+ options.src = src;
+ options.on('pipe', function () {
+ console.error("You have already piped to this stream. Pipeing twice is likely to break the request.")
+ })
+ })
+
+ process.nextTick(function () {
+ if (options.body) {
+ options.req.write(options.body);
+ options.req.end();
+ } else if (options.requestBodyStream) {
+ console.warn("options.requestBodyStream is deprecated, please pass the request object to stream.pipe.")
+ options.requestBodyStream.pipe(options);
+ } else if (!options.src) {
+ options.req.end();
+ }
+ options.ntick = true;
+ })
+}
+Request.prototype.pipe = function (dest) {
+ if (this.response) throw new Error("You cannot pipe after the response event.")
+ this.dests.push(dest);
+}
+Request.prototype.write = function () {
+ if (!this.req) throw new Error("This request has been piped before http.request() was called.");
+ this.req.write.apply(this.req, arguments);
+}
+Request.prototype.end = function () {
+ if (!this.req) throw new Error("This request has been piped before http.request() was called.");
+ this.req.end.apply(this.req, arguments);
+}
+Request.prototype.pause = function () {
+ if (!this.req) throw new Error("This request has been piped before http.request() was called.");
+ this.req.pause.apply(this.req, arguments);
+}
+Request.prototype.resume = function () {
+ if (!this.req) throw new Error("This request has been piped before http.request() was called.");
+ this.req.resume.apply(this.req, arguments);
+}
+
+function request (options, callback) {
+ if (callback) options.callback = callback;
+ var r = new Request(options);
+ r.request();
+ return r;
+}
+
+module.exports = request;
+
+request.defaults = function (options) {
+ var def = function (method) {
+ var d = function (opts, callback) {
+ for (i in options) {
+ if (!opts[i]) opts[i] = options[i];
+ return method(opts, callback);
+ }
+ }
+ return d;
+ }
+ de = def(request);
+ de.get = def(request.get);
+ de.post = def(request.post);
+ de.put = def(request.put);
+ de.head = def(request.head);
+ de.del = def(request.del);
+ return d;
+}
+
+request.get = request;
+request.post = function (options, callback) {
+ options.method = 'POST';
+ return request(options, callback);
+};
+request.put = function (options, callback) {
+ options.method = 'PUT';
+ return request(options, callback);
+};
+request.head = function (options, callback) {
+ options.method = 'HEAD';
+ if (options.body || options.requestBodyStream || options.json || options.multipart) {
+ throw new Error("HTTP HEAD requests MUST NOT include a request body.");
+ }
+ return request(options, callback);
+};
+request.del = function (options, callback) {
+ options.method = 'DELETE';
+ return request(options, callback);
+}
14 node_modules/request/package.json
@@ -0,0 +1,14 @@
+{ "name" : "request"
+, "description" : "Simplified HTTP request client."
+, "tags" : ["http", "simple", "util", "utility"]
+, "version" : "1.9.5"
+, "author" : "Mikeal Rogers <mikeal.rogers@gmail.com>"
+, "repository" :
+ { "type" : "git"
+ , "url" : "http://github.com/mikeal/request.git"
+ }
+, "bugs" :
+ { "web" : "http://github.com/mikeal/request/issues" }
+, "engines" : ["node >= 0.3.6"]
+, "main" : "./main"
+}
11 package.json
@@ -0,0 +1,11 @@
+{
+ "name": "gdata-js",
+ "description": "Simple OAuth 2.0 GData API client",
+ "version": "0.0.0",
+ "author": "Simon Murtha-Smith <simon@murtha-smith.com>",
+ "keywords": ["google", "gdata"],
+ "main" : "gdata.js",
+ "dependencies": {},
+ "repository" : {"type": "git" , "url": "http://github.com/smurthas/gdata-js.git" },
+ "engines": { "node": ">=0.4.6 <0.4.8" }
+}
42 test.js
@@ -0,0 +1,42 @@
+var request = require('request');
+var querystring = require('querystring');
+var fs = require('fs');
+
+// get an clientID and clientSecret at https://code.google.com/apis/console/
+var gdata = require('./gdata')('yourClientID', 'yourClientSecret', 'http://localhost:8553/')
+var scope = 'https://www.google.com/m8/feeds/'; //contacts
+
+var express = require('express'),
+ connect = require('connect'),
+ app = express.createServer(connect.bodyParser());
+
+var token;
+app.get('/', function (req, res) {
+ gdata.getAccessToken(scope, req, res, function(err, tkn) {
+ if(err) {
+ console.error('oh noes!', err);
+ res.writeHead(500);
+ res.end('error: ' + JSON.stringify(err));
+ } else {
+ token = tkn;
+ res.redirect('/getStuff');
+ }
+ });
+});
+
+app.get('/getStuff', function(req, res) {
+ getFeed('https://www.google.com/m8/feeds/contacts/default/full', {oauth_token:token.access_token, 'max-results':3},
+ function(err, feed) {
+ res.writeHead(200);
+ for(var i in feed.feed.entry) {
+ res.write(JSON.stringify(feed.feed.entry[i]['title']));
+ res.write(JSON.stringify(feed.feed.entry[i]['gd$email']));
+ res.write('\n\n')
+ }
+ res.end();
+ });
+})
+
+app.listen(8553);
+console.log('open http://localhost:8553');
+

0 comments on commit 6d16a70

Please sign in to comment.
Something went wrong with that request. Please try again.