├── .gitignore ├── Makefile ├── Readme.md ├── index.js ├── lib └── linkedin_client.js ├── package.json └── tests └── linkedin_test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NODE = node 2 | 3 | test: 4 | @$(NODE) tests/linkedin_test.js 5 | 6 | .PHONY: test 7 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # linkedin-js 2 | 3 | Easy peasy linkedin client for connect. 4 | 5 | ``` bash 6 | npm install linkedin-js 7 | ``` 8 | 9 | ## Usage 10 | 11 | linkedin-js has two methods. 12 | 13 | * getAccesToken(req, res, callback): Uses oAuth module to retrieve the access_token 14 | * apiCall(http_method, path, params, callback): Does a call to the linkedin API. 15 | 16 | Params are sent as JSON. 17 | Params must contain the token. 18 | 19 | [Using JSON with linkedin API](http://developer.linkedin.com/docs/DOC-1203) 20 | 21 | ## Example using express.js 22 | 23 | ``` javascript 24 | var express = require('express') 25 | , linkedin_client = require('linkedin-js')('key', 'secret', 'http://localhost:3003/auth') 26 | , app = express.createServer( 27 | express.cookieParser() 28 | , express.session({ secret: "string" }) 29 | ); 30 | 31 | app.get('/auth', function (req, res) { 32 | // the first time will redirect to linkedin 33 | linkedin_client.getAccessToken(req, res, function (error, token) { 34 | // will enter here when coming back from linkedin 35 | req.session.token = token; 36 | 37 | res.render('auth'); 38 | }); 39 | }); 40 | 41 | app.post('/message', function (req, res) { 42 | linkedin_client.apiCall('POST', '/people/~/shares', 43 | { 44 | token: { 45 | oauth_token_secret: req.session.token.oauth_token_secret 46 | , oauth_token: req.session.token.oauth_token 47 | } 48 | , share: { 49 | comment: req.param('message') 50 | , visibility: {code: 'anyone'} 51 | } 52 | } 53 | , function (error, result) { 54 | res.render('message_sent'); 55 | } 56 | ); 57 | }); 58 | 59 | app.listen(3003); 60 | ``` 61 | 62 | ## Test 63 | 64 | linkdin is fully tested using [testosterone](https://github.com/masylum/testosterone) 65 | 66 | ``` bash 67 | make 68 | ``` 69 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/linkedin_client'); 2 | -------------------------------------------------------------------------------- /lib/linkedin_client.js: -------------------------------------------------------------------------------- 1 | var url = require('url') 2 | , http = require('http') 3 | , OAuth = require('oauth').OAuth 4 | , querystring = require('querystring') 5 | , memoize = {}; 6 | 7 | module.exports = function (key, secret, redirect) { 8 | if (memoize[key + secret + redirect]) { 9 | return memoize[key + secret + redirect]; 10 | } 11 | 12 | var CLIENT = { 13 | oauth: new OAuth( 14 | 'https://api.linkedin.com/uas/oauth/requestToken' 15 | , 'https://api.linkedin.com/uas/oauth/accessToken' 16 | , key 17 | , secret 18 | , '1.0' 19 | , redirect 20 | , 'HMAC-SHA1' 21 | , null 22 | , {'Accept': '*/*', 'Connection': 'close'} 23 | ) 24 | } 25 | , paramAppender = "?" 26 | , hasParameters = /\/*\?/i 27 | , _rest_base = 'http://api.linkedin.com/v1'; 28 | 29 | memoize[key + secret + redirect] = CLIENT; 30 | 31 | /** 32 | * Does an API call to linkedin and callbacks 33 | * when the result is available. 34 | * 35 | * @param {String} method 36 | * @param {String} path 37 | * @param {Object} params 38 | * @param {Function} callback 39 | * @return {Request} 40 | */ 41 | CLIENT.apiCall = function (method, path, params, callback) { 42 | var token = params.token; 43 | 44 | delete params.token; 45 | 46 | function requestCallback(callback) { 47 | return function (error, data, response) { 48 | if (error) { 49 | callback(error, null); 50 | } else { 51 | try { 52 | callback(null, JSON.parse(data)); 53 | } catch (exc) { 54 | callback(exc, null); 55 | } 56 | } 57 | }; 58 | } 59 | 60 | if (method.toUpperCase() === 'GET') { 61 | params.format = 'json'; 62 | 63 | if (path.match(hasParameters)) { 64 | paramAppender = "&"; 65 | } 66 | 67 | return CLIENT.oauth.get( 68 | _rest_base + path + paramAppender + querystring.stringify(params) 69 | , token.oauth_token 70 | , token.oauth_token_secret 71 | , requestCallback(callback) 72 | ); 73 | } else if (method.toUpperCase() === 'POST') { 74 | return CLIENT.oauth.post( 75 | _rest_base + path 76 | , token.oauth_token 77 | , token.oauth_token_secret 78 | , params 79 | , 'application/json; charset=UTF-8' 80 | , requestCallback(callback) 81 | ); 82 | } 83 | }; 84 | 85 | /** 86 | * Redirects to linkedin to retrieve the token 87 | * or callbacks with the proper token 88 | * 89 | * @param {Request} req 90 | * @param {Response} res 91 | * @param {Function} callback 92 | */ 93 | CLIENT.getAccessToken = function (req, res, callback) { 94 | var parsed_url = url.parse(req.url, true) 95 | , protocol = req.socket.encrypted ? 'https' : 'http' 96 | , callback_url = protocol + '://' + req.headers.host + parsed_url.pathname 97 | , has_token = parsed_url.query && parsed_url.query.oauth_token 98 | , has_secret = req.session && req.session.auth && req.session.auth.linkedin_oauth_token_secret; 99 | 100 | // var query = url.parse(req.url, true).query 101 | // , auth = req.session && req.session.auth; 102 | 103 | // Access token 104 | if (has_token && has_secret) { 105 | 106 | CLIENT.oauth.getOAuthAccessToken( 107 | parsed_url.query.oauth_token 108 | , req.session.auth.linkedin_oauth_token_secret 109 | , parsed_url.query.oauth_verifier 110 | , function (error, oauth_token, oauth_token_secret, additionalParameters) { 111 | if (error) { 112 | callback(error, null); 113 | } else { 114 | callback(null, {oauth_token: oauth_token, oauth_token_secret: oauth_token_secret}); 115 | } 116 | } 117 | ); 118 | 119 | // Request token 120 | } else { 121 | 122 | CLIENT.oauth.getOAuthRequestToken( 123 | {oauth_callback: callback_url} 124 | , function (error, oauth_token, oauth_token_secret, oauth_authorize_url, additional_parameters) { 125 | if (error) { 126 | callback(error, null); 127 | } else { 128 | req.session.linkedin_redirect_url = req.url; 129 | req.session.auth = req.session.auth || {}; 130 | req.session.auth.linkedin_oauth_token_secret = oauth_token_secret; 131 | req.session.auth.linkedin_oauth_token = oauth_token; 132 | res.redirect("https://www.linkedin.com/uas/oauth/authenticate?oauth_token=" + oauth_token); 133 | } 134 | } 135 | ); 136 | } 137 | }; 138 | 139 | return CLIENT; 140 | }; 141 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "linkedin-js", 3 | "description": "Minimalistic linkedin API client", 4 | "version": "0.1.4", 5 | "author": "Pau Ramon ", 6 | "keywords": ["linkedin"], 7 | "dependencies": { "oauth": "0.9.0" }, 8 | "devDependencies": { 9 | "testosterone": "1.2.0", 10 | "gently": "0.9.1" 11 | }, 12 | "main" : "lib/linkedin_client.js", 13 | "directories" : { "lib" : "./lib" }, 14 | "repository" : {"type": "git" , "url": "http://github.com/masylum/linkedin-js.git" }, 15 | "engines": { "node": ">= 0.2.0" } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /tests/linkedin_test.js: -------------------------------------------------------------------------------- 1 | var testosterone = require('testosterone')({title: 'models/linkedin'}) 2 | , assert = testosterone.assert 3 | , querystring = require('querystring') 4 | , gently = global.GENTLY = new (require('gently')) 5 | , key = 'foo' 6 | , secret = 'bar' 7 | , redirect = 'http://google.com' 8 | , token = {oauth_token: '123', oauth_token_secret: '456'} 9 | , linkedin_client = require('../')(key, secret, redirect) 10 | ; 11 | 12 | testosterone 13 | 14 | .add('`apiCall` GET', function (done) { 15 | var callback; 16 | 17 | gently.expect(linkedin_client.oauth, 'get', function (_path, _token, _secret, _callback) { 18 | assert.equal(_path, 'http://api.linkedin.com/v1/people/id=abcdefg?format=json'); 19 | assert.equal(_token, token.oauth_token); 20 | assert.equal(_secret, token.oauth_token_secret); 21 | assert.equal(_callback, callback); 22 | _callback(); 23 | }); 24 | 25 | callback = gently.expect(function (error, response, body) { 26 | done(); 27 | }); 28 | 29 | linkedin_client.apiCall('GET', '/people/id=abcdefg', {token: token}, callback); 30 | }) 31 | 32 | .add('`apiCall` POST', function (done) { 33 | var callback; 34 | 35 | gently.expect(linkedin_client.oauth, 'post', function (_path, _token, _secret, _params, _accept_header, _callback) { 36 | assert.equal(_path, 'http://api.linkedin.com/v1/people/~/person-activities'); 37 | assert.equal(_token, token.oauth_token); 38 | assert.equal(_secret, token.oauth_token_secret); 39 | assert.deepEqual(_params, {contentType: 'linkedin-html', body: 'hola', '_locale': 'en-US'}); 40 | assert.deepEqual(_accept_header, 'application/json; charset=UTF-8'); 41 | assert.equal(_callback, callback); 42 | _callback(); 43 | }); 44 | 45 | callback = gently.expect(function (error, response, body) { 46 | done(); 47 | }); 48 | 49 | linkedin_client.apiCall( 50 | 'POST' 51 | , '/people/~/person-activities' 52 | , {token: token, contentType: 'linkedin-html', body: 'hola', '_locale': 'en-US'} 53 | , callback); 54 | }) 55 | 56 | .add('`apiCall` GET', function (done) { 57 | var callback; 58 | 59 | gently.expect(linkedin_client.oauth, 'get', function (_path, _token, _secret, _callback) { 60 | assert.equal(_path, 'http://api.linkedin.com/v1/people-search?keywords=linkedin&format=json'); 61 | assert.equal(_token, token.oauth_token); 62 | assert.equal(_secret, token.oauth_token_secret); 63 | assert.equal(_callback, callback); 64 | _callback(); 65 | }); 66 | 67 | callback = gently.expect(function (error, response, body) { 68 | done(); 69 | }); 70 | 71 | linkedin_client.apiCall('GET', '/people-search?keywords=linkedin', {token: token}, callback); 72 | }) 73 | 74 | .run(); 75 | --------------------------------------------------------------------------------