Skip to content
Browse files

0.0.2 refactored example, paged search, polished client code

  • Loading branch information...
1 parent 80aa78b commit af52dc3822c428f953700352779e53c5e974d232 @haraldrudell committed Apr 23, 2012
Showing with 192 additions and 130 deletions.
  1. +35 −32 example/example.js
  2. +48 −0 example/httpserver.js
  3. +24 −6 example/jsonhtml.js
  4. +57 −0 example/presentation.js
  5. +0 −68 example/server.js
  6. +10 −0 example/utils.js
  7. +16 −23 lib/linkedin.js
  8. +2 −1 package.json
View
67 example/example.js
@@ -2,57 +2,53 @@
// working LinkedIn rest api example
// imports
+// the client code for this module that we should demonstrate
+// https://github.com/haraldrudell/linkedin
var linkedin = require('../lib/linkedin')
var utils = require('./utils')
-var server = require('./server')
// the api call to display
LinkedInUri = '/people/~:(picture-url,first-name,last-name,headline)'
// get our configuration
-linkedinJson = 'linkedin.json'
-var defaults = utils.getDefaults(linkedinJson, [ utils.getHomeFolder(), __dirname + '/..' ])
-if (!defaults) throw(Error('Problem finding the file ' + linkedinJson))
+var defaults = utils.getDefaults('linkedin.json')
var port = utils.getPort(defaults.hostUrl)
// launch our server so we can display things to the user
// and receive redirects from LinkedIn after authoriztion
-console.log('starting server:', defaults.hostUrl)
-var aServer = server.server(port,
- defaults.completedUri, completedHandler,
- defaults.authenticateUri, authenticateHandler,
- console.log, defaults.register)
+var app = utils.createServer()
+utils.handleHttp(app, defaults, completedHandler,
+ authenticateHandler, console.log)
+app.listen(port)
-// authorize the web application for the user
+// prepare the local client code
var client = linkedin.getApiClient(defaults)
-// here you could apply previously obtained credential for this user
+// apply access credentials that we stored for this user
+// the credentials would have been obtained at a prior authorization
var obj = utils.readStore()
if (obj) client.applyAccessCredentials(obj)
-// if we have credentials, use the api
-if (client.hasAccess()) {
- // display our server to the user
- utils.browseTo(defaults.authorize_callback)
-}
+// if we have credentials, immediately launch the browser to display the api call results
+if (client.hasAccess()) utils.browseTo(defaults.authorize_callback)
-// otherwise, authorize the LinkedIn web app first
-client.authorize(function(err, url) {
+// begin authorization flow with LinkedInotherwise, authorize the LinkedIn web app first
+else client.authorize(function(err, url) {
if (err) console.log('Error when retrieving request token from LinkedIn:', err)
- else {
- // we successfully obtained Oauth request tokens
+ else
+ // we successfully obtained Oauth request tokens
// display the authorization web page to the user
+ // user will finally be redirected to completedUri by LinkedIn
utils.browseTo(url)
-
- // user will be redirected to completedUri by LinkedIn
- }
})
-// invoked by server when completeUri is requested, server expects json
-// for requests to the redirect after authorizing, process and obtain Oauth access token
+// An authroization attempt has been completed
+// capture access credentials
+// display api-call results to user
function completedHandler(queryObject, callback) {
- // for requests to the redirect after authorizing, process and obtain Oauth access token
+
+ // provide authroization results to local client code
client.handleAuthorizing(queryObject, function(err, possibleAccessCredentials) {
// some error trying to get access tokens
if (err) callback(err, null)
@@ -64,16 +60,18 @@ function completedHandler(queryObject, callback) {
utils.writeStore(possibleAccessCredentials)
}
- // render some final data to the user
+ // verify that our app is authorized
if (!client.hasAccess()) {
var json = ({ issue: 'You have not authorized this application' })
callback(null, json)
- } else doApi(callback)
+ } else
+ // execute the api call
+ doApi(callback)
}
})
}
-// we are authorized and want to display something in json format
+// we are authorized and want to display something fun!
function doApi(callback) {
// send an api call to LinkedIn
client.api(LinkedInUri, function(err, json) {
@@ -82,10 +80,15 @@ function doApi(callback) {
})
}
-// user clicked reauthenticate, browser loaded autenticateUri, and here's our handler
+// the user clicked reauthenticate button on the completed page
+// the browser is fetching our authentication url
function authenticateHandler(callback) {
- // clear possible authentication
+
+ // clear possible exisiting credentials
client.clearAccess()
- // re-initialize authentication, let server handle redirect to completedUri
+
+ // have our client code re-initialize authentication
+ // this will redirect to LinkedIn's authorization page
+ // and eventually we will get a request for completedUri
client.authorize(callback)
}
View
48 example/httpserver.js
@@ -0,0 +1,48 @@
+// httpserver.js
+// provide an http server based on the built-in http module with connect-like features
+
+//http://nodejs.org/docs/latest/api/http.html
+var http = require('http')
+// http://nodejs.org/docs/latest/api/url.html
+var url = require('url')
+exports.createServer = createServer
+
+function createServer() {
+ var logger = function () {}
+ var handlers = {}
+
+ var instance = http.createServer(listener)
+ instance.get = get
+ instance.setLogger = setLogger
+ return instance
+
+ // incoming message from the socket we listen to
+ // req: http.serverRequest constructor http.IncomingMessage
+ // res http.ServerResponse
+ // express handlers are (req, res, next)
+ function listener(req, res) {
+ var parsedUrl = url.parse(req.url, true)
+ req.param = parsedUrl.query
+ var handler = handlers[parsedUrl.pathname] || renderNotFound
+ handler(req, res, renderNotFound)
+
+ function renderNotFound() {
+ var statusCode = 404
+ var text = http.STATUS_CODES[statusCode]
+ logger(text + ': ', parsedUrl.pathname)
+ res.writeHead(statusCode, {"Content-Type": "text/plain"})
+ res.end(text)
+ }
+
+ }
+
+ // implement express: app.get
+ function get(uri, handler) {
+ handlers[uri] = handler
+ }
+
+ function setLogger(log) {
+ logger = log
+ }
+
+}
View
30 example/jsonhtml.js
@@ -1,43 +1,60 @@
// htmljson.js
// translate a json object to an html document
-// wrap body to become an html document
+
+//imports
+// http://nodejs.org/docs/latest/api/fs.html
+var fs = require('fs')
+
+// exports
exports.jsonHtml = jsonHtml
+// wrap body in html, insert title
function html(body, title) {
- if (!title) title = 'Title'
return '<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><title>' +
- title +
- '</title><link href="/server.css" rel="stylesheet" type="text/css"></head><body>\n' +
+ (title || 'LinkedIn client demo') +
+ '</title><style type="text/css">' + css() + '</style></head><body>\n' +
body +
'\n</body></html>'
}
-// convert data from json format to html
+function css() {
+ return fs.readFileSync(__dirname + '/server.css')
+}
+
+// convert object data to an html document string
function jsonHtml(data, authenticateUri, register) {
+
+ // re-authenticate call to action
var myHtml = '<div><button onclick="parent.location=\''+
authenticateUri +'\'">Authenticate Again</button>'
+ // register API key call to action
if (register) {
myHtml += '<br/>Get your own API Key at <a href=https://www.linkedin.com/secure/developer >https://www.linkedin.com/secure/developer</a>'
}
+ // LinkedIn profile headline
if (data.headline) {
myHtml += '<h1>' +data.headline + '</h1>'
}
+ // LinkedIn profile picture
if (data.pictureUrl) {
myHtml += '<img alt="Profile Picture" src=' +data.pictureUrl + ' />'
}
+ // LinkedIn profile full name
var fullName = getName(data)
if (fullName) myHtml += '<p>' + fullName + '</p>'
myHtml += '</div>'
+ // LinkedIn response field-by-field
myHtml += allFields(data)
- return html(myHtml, fullName || 'LinkedIn response')
+ return html(myHtml, fullName)
}
+// get full name from LinkedIn profile response
function getName(data) {
var fullName
@@ -51,6 +68,7 @@ function getName(data) {
return fullName
}
+// provide verbose LinkedIn response
function allFields(data) {
var count = 0
var result = '<ol>'
View
57 example/presentation.js
@@ -0,0 +1,57 @@
+// presentation.js
+// handle interface between server's html and app's json
+
+// http://nodejs.org/docs/latest/api/fs.html
+var fs = require('fs')
+
+var jsonhtml = require('./jsonhtml')
+
+exports.handleHttp = handleHttp
+
+function handleHttp(app, defaults, completedHandler, authenticateHandler, logger) {
+ if (!logger) logger = function () {}
+ else if (app.setLogger) app.setLogger(logger)
+
+ console.log('server at:', defaults.hostUrl)
+
+ app.get(defaults.authenticateUri, function(req, res) {
+ authenticateHandler(function(err, url) {
+ render(err, 'Redirecting:' + url, res, url)
+ })
+ })
+
+ app.get(defaults.completedUri, function (req, res) {
+ completedHandler(req.param, function(err, data) {
+ if (!err) {
+ // we have object-format results that should be presented as html
+ data = jsonhtml.jsonHtml(data, defaults.authenticateUri, defaults.register)
+ }
+ render(err, data, res)
+ })
+ })
+
+ // render text response and proper status code 200, 302, 500
+ function render(err, data, res, redirectUrl) {
+ var isHtml = false
+ var statusCode = 200
+ var headers = {}
+
+ if (!err) {
+ if (redirectUrl) {
+ statusCode = 302
+ headers['Location'] = redirectUrl
+ }
+ if (typeof data != 'string') throw Error('Response not string')
+ isHtml = data[0] == '<'
+ } else {
+ statusCode = 500
+ data = err instanceof Object ?
+ err.message || JSON.stringify(err) :
+ err.toString()
+ }
+ headers['Content-Type'] = isHtml ? "text/html" : "text/plain"
+ res.writeHead(statusCode, headers)
+ res.end(data)
+ }
+
+}
View
68 example/server.js
@@ -1,68 +0,0 @@
-// server.js
-// provide an http based server
-// similar function to what is provided by express, connect or other web framework
-
-//http://nodejs.org/docs/latest/api/http.html
-var http = require('http')
-// http://nodejs.org/docs/latest/api/url.html
-var url = require('url')
-// http://nodejs.org/docs/latest/api/fs.html
-var fs = require('fs')
-
-var jsonhtml = require('./jsonhtml')
-
-exports.server = server
-
-function server(port,
- completedUri, completedHandler,
- authenticateUri, authenticateHandler,
- logger, register) {
- if (!logger) logger = function () {}
- // start our web server that the user is redirected to after authentication
- var server = http.createServer(function(req, res) {
- var parsedUrl = url.parse(req.url, true)
- if (parsedUrl.pathname == completedUri) {
- completedHandler(parsedUrl.query, function(err, data) {
- if (err) {
- var string = err instanceof Object ?
- err.message || JSON.stringify(err) :
- err.toString()
- res.writeHead(500, {"Content-Type": "text/plain"})
- res.end(string)
- } else {
- // api call successful rendering
- res.writeHead(200, {"Content-Type": "text/html"})
- res.end(jsonhtml.jsonHtml(data, authenticateUri, register))
- }
- })
- } else if (parsedUrl.pathname == authenticateUri) {
- authenticateHandler(function(err, url) {
- if (!err) {
- res.writeHead(302, {"Content-Type": "text/plain",
- 'Location': url})
- res.end('Redirecting:' + url)
- } else {
- res.writeHead(500, {"Content-Type": "text/plain"})
- res.end(err.toString())
- }
-
- })
- } else if (parsedUrl.pathname == '/server.css') {
- console.log(parsedUrl.pathname)
- fs.readFile(__dirname + '/server.css', function(error, content) {
- if (!error) {
- res.writeHead(200, { 'Content-Type': 'text/css' })
- res.end(content)
- } else {
- res.writeHead(500)
- res.end()
- }
- })
- } else {
- logger('404 response for url:', parsedUrl.pathname)
- res.writeHead(404, {"Content-Type": "text/plain"})
- res.end('not found')
- }
- }).listen(port)
- return server
-}
View
10 example/utils.js
@@ -11,13 +11,19 @@ var optsutils = require('../lib/optsutils')
var spawn = require('child_process').spawn
var credentialstore = require('./credentialstore')
+// for re-export
+var httpserver = require('./httpserver')
+var presentation = require('./presentation')
+
// exports
exports.browseTo = browseTo
exports.getPort = getPort
exports.readStore = credentialstore.readStore
exports.writeStore = credentialstore.writeStore
exports.getDefaults = getDefaults
exports.getHomeFolder = getHomeFolder
+exports.createServer = httpserver.createServer
+exports.handleHttp = presentation.handleHttp
var fields = [ "consumerKey", "consumerSecret",
"hostUrl", "authenticateUri", "completedUri"
@@ -101,6 +107,8 @@ function getPort(hostUrl) {
function getDefaults(filename, folders) {
var result = false
+ if (!folders) folders = [ getHomeFolder(), __dirname + '/..' ]
+
// look in all provided folders
if (Array.isArray(folders)) {
folders.every(function(folder) {
@@ -114,6 +122,8 @@ function getDefaults(filename, folders) {
result = load(filename, folder)
}
+ if (!result) throw Error('Problem finding the file ' + filename)
+
return result
function load(filename, folder) {
View
39 lib/linkedin.js
@@ -99,7 +99,7 @@ function authorize(callback) {
// true: client has access credentials
function hasAccess() {
- return this.access != null
+ return !!this.access
}
function handleAuthorizing(queryParams, callback) {
@@ -142,8 +142,7 @@ function handleAuthorizing(queryParams, callback) {
}
var ok = self.applyAccessCredentials(access)
if (ok !== true) {
- error = Error('Missing access token fields from LinkedIn:' +
- fields.join(','))
+ error = Error('Bad access token fields from LinkedIn:')
} else {
result = access
}
@@ -210,7 +209,7 @@ function clearAccess() {
delete this.oauth_token_secret
}
-// retrieve all resukts for a paged request
+// retrieve all results for a paged request
// query: keys:
// - url the search url that can have &start and &count appended to it
// - filter: an optional filter with key: fieldname, value: required value
@@ -219,18 +218,18 @@ function clearAccess() {
// -- if calback returns false, the api call is terminated
function pagedSearch(query, callback) {
var self = this
- var result = querry.stream ? null : []
+ var result = query.stream ? null : []
var c = {
+
+ // fetch control
// concurrent requests
max: 5,
// key: first item requested, value: don't care
pending: {},
-
- // fetch control
pageSize: 25,
nextToFetch: 0,
maxToFetch: -1,
- // key: first item, value: people array
+ // key: first item, value: results array
received: {},
// processing control
@@ -255,7 +254,6 @@ function pagedSearch(query, callback) {
var myFirst = c.nextToFetch
c.nextToFetch += c.pageSize
var url = query.uri + '&start=' + myFirst + '&count=' + c.pageSize
- console.log('submit:', myFirst)
c.pending[myFirst] = true
self.api(url, function(err, json) {
delete c.pending[myFirst]
@@ -272,7 +270,7 @@ function pagedSearch(query, callback) {
if (total) c.maxToFetch = total
}
- // how many did we get
+ // ensure we got a full page
var count = 0
var values = json.people.values
if (Array.isArray(values)) count = values.length
@@ -285,20 +283,20 @@ function pagedSearch(query, callback) {
callback(err, json)
}
- // we know there is one pending slot availabke
- console.log('inner recursion')
- fetch()
-
- // forward to processing
if (count > 0) {
+ // we know there is one pending slot available
+ fetch()
+
+ // forward to process
c.received[myFirst] = values
if (c.nextToProcess == myFirst) process()
}
// there will be at least one request
// we either got an error or get here
if (Object.keys(c.pending).length == 0) {
- // there are no more requests
+ // this is the response from the last request
+
// if there was no data, go complete
if (c.maxToFetch == -1) {
// we never got a total
@@ -321,26 +319,21 @@ function pagedSearch(query, callback) {
}
})
// after submitting a request, submit another one to capacity
- console.log('outer recursion')
if (Object.keys(c.pending).length < c.max) fetch()
}
}
} // submit
-var maxD = 0
- // entered whenever data added to an empty buffer
+ // the next data to process is available
function process() {
- console.log('process')
while (!c.cancelled) {
// get the data
var myFirst = c.nextToProcess
var values = c.received[myFirst]
if (!values) break
// process the items
- console.log('processing:', myFirst, values.length)
values.every(function(value) {
- if (value.distance > maxD) maxD = value.distance
if (matchWithFilter(query.filter, value)) {
if (query.stream) {
if (!callback(null, value)) {
@@ -365,7 +358,7 @@ var maxD = 0
}
function complete() {
- console.log('complete', maxD)
callback(null, result)
}
+
}
View
3 package.json
@@ -1,7 +1,8 @@
{
"name": "linkedin",
"description": "The absolute easies way to enable node.js for LinkedIn rest api. Includes runnable demo by Harald Rudell.",
- "version": "0.0.1",
+ "author": "Harald Rudell <harald@allgoodapps.com>",
+ "version": "0.0.2",
"contributors": [
{
"name": "Harald Rudell",

0 comments on commit af52dc3

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