-
Notifications
You must be signed in to change notification settings - Fork 123
OAuth 1 Connector Example
The easiest way to get going is to copy and paste the code in Locker/Connectors/skeleton into a new directory for your connector. Let's pretend there isn't a yet a Twitter connector (we'll use "TwitterExample" as the name so nothing bad happens):
cd Connectors
mkdir TwitterExample
cd TwitterExample
cp -r ../skeleton/* .
The first thing we'll want to do is change the name of the .connector manifest file:
mv skeleton.connector TwitterExample.connector
Now let's edit that file. The only things that MUST be changed are the handle, mongoCollections, and provides fields.
{
"title":"Twitter Example",
"action":"Connect to a Twitter account",
"desc":"Collect and sync my data from my Twitter account",
"run":"node init.js",
"status":"unstable",
"handle":"twitterexample",
"mongoCollections": [],
"provides":["contact/twitterexample"]
}
More info can be found about the provides
field on the service types page.
Next let's take a look at the init.js
file. The require('connector/client').init({...})
function loads the common connector bootstrap file and initializes it with some startup info. For more details about the init.js file and client.init function, see the Custom Files section below.
OAuth 1 needs cookies, so in the init.js
file:
require('connector/client').init({"enableCookies" : true});
We don't have a generic OAuth 1 module like the OAuth 2 module, so we'll have to write our own. In this case, the twitter-js client library, provides almost all of the functionality, we just need to provide some endpoints. In the TwitterExample directory, create an auth.js
file that looks like this:
var fs = require("fs");
var uri,
url = require('url'),
completedCallback = null;
exports.auth = {};
// Check if exports.auth contains the required properties (consumerKey, consumerSecret, and token)
// if not, read it from disk and try again
exports.isAuthed = function() {
if(!exports.auth)
exports.auth = {};
// Already have the stuff read
if(exports.auth.consumerKey && exports.auth.consumerSecret && exports.auth.token)
return true;
try {
// Try and read it in
var authData = JSON.parse(fs.readFileSync("auth.json"));
exports.auth = authData;
if(authData.consumerKey && authData.consumerSecret && authData.token)
return true;
} catch (E) {
// TODO: Could actually check the error type here
}
return false;
};
// The required exported function
// Checks if there is a valid auth, callback immediately (and synchronously) if there is
// If there isn't, adds /auth and /saveAuth endpoint to the app
exports.authAndRun = function(app, externalUrl, onCompletedCallback) {
if (exports.isAuthed()) {
onCompletedCallback();
return;
}
// not auth'd yet, save the app's uri and the function to call back to later
uri = externalUrl;
completedCallback = onCompletedCallback;
app.get("/auth", handleAuth);
app.get("/saveAuth", saveAuth);
};
// Handles requests to the /auth endpoint
// This will be called 3 times:
// 1. To captue the consumerKey and consumerSecret via a form (which posts to /saveAuth)
// 2. Uses the consumerKey and consumerSecret to contruct the twitter url to redirect to
// 3. Capture the access token when the person returns from twitter
function handleAuth(req, res) {
if(!exports.auth)
exports.auth = {};
if(!(exports.auth.consumerKey && exports.auth.consumerSecret)) {
res.writeHead(200, { 'Content-Type': 'text/html' });
var h = "<html><head><title>Twitter Connector</title></head><body>";
h += "Enter your personal Twitter app info that will be used to sync your data.<br/><br/>"
h += "Create a new one <a href='http://dev.twitter.com/apps/new' target='_blank'>here</a> "
h += "using the callback url of <input type='text' readonly='true' value='http://"
h += url.parse(uri).host.replace("localhost", "127.0.0.1")+"/'/> <br/><br/>"
h += "<form method='get' action='saveAuth'>"
h += "Consumer Key: <input name='consumerKey'><br/>"
h += "Consumer Secret: <input name='consumerSecret'><br/>"
h += "<br/><input type='submit' value='Save'>"
h += "</form></body></html>"
res.end(h);
} else if(!exports.auth.token) {
if(typeof req.url === 'string') // hack to redirect properly
req.url = uri + req.url.substring(1);
require('twitter-js')(exports.auth.consumerKey, exports.auth.consumerSecret, uri + 'auth')
.getAccessToken(req, res, function(err, newToken) {
if(err)
console.error(err);
if(newToken != null) {
exports.auth.token = newToken;
fs.writeFileSync('auth.json', JSON.stringify(exports.auth));
completedCallback();
res.end("<script type='text/javascript'>if (window.opener) { " +
"window.opener.location.reload(true); } window.close(); </script>");
}
});
} else {
res.redirect(uri);
completedCallback();
}
}
// Save the consumerKey and consumerSecret
function saveAuth(req, res) {
if(!req.param('consumerKey') || !req.param('consumerSecret')) {
res.writeHead(400);
res.end("missing field(s)?");
} else {
exports.auth.consumerKey = req.param('consumerKey');
exports.auth.consumerSecret = req.param('consumerSecret');
res.redirect(uri + 'auth');
}
}
Next, let's edit the sync.js
file. Let's change the syncItems
function to syncProfile
and add a few other functions:
// Syncs the profile of the auth'd user
exports.syncProfile = function(callback) {
getUserInfo(function(err, newUserInfo) {
profile = newUserInfo;
lfs.writeObjectToFile('profile.json', profile);
callback(err, newUserInfo);
});
}
// Gets the profile of the auth'd user
function getUserInfo(callback) {
if(!getTwitterClient())
return;
getTwitterClient().apiCall('GET', '/account/verify_credentials.json',
{token:auth.token, include_entities:true}, callback);
}
var twitterClient;
// Ensures that we are always working with the same, valid and auth'd twitter client object
function getTwitterClient() {
if(!twitterClient && auth && auth.consumerKey && auth.consumerSecret)
twitterClient = require('twitter-js')(auth.consumerKey, auth.consumerSecret);
return twitterClient;
}
Now let's change sync-api.js
to call our new syncProfile
function and make a slight tweak to the index
function:
function index(req, res) {
if(!(auth && auth.token))
...
}
function items(req, res) {
...
sync.syncProfile(function(err, repeatAfter, diaryEntry) {
...
});
}
At this point, the connector is in a functioning state, but if we'd like, we can tailor the sync-api.js
file to represent what we are doing (if not, JMP Done!
). Let's change the name of the /items
endpoint to /updateProfile
:
app.get('/items', items);
becomes:
app.get('/updateProfile', updateProfile);
and then we change the items
function to updateFriends
:
// this is the basic structure of an endpoint for something you'd be parsing.
function updateProfile(req, res) {
...
locker.at('/updateProfile', repeatAfter);
...
}
and finally edit the html in the index function to point to /updateProfile instead of /items:
res.end("<html>found a token, load <a href='updateProfile'>profile</a>");
All we need to do is install the twitter-js
node module and then run.
cd ../..
npm install twitter-js
node lockerd.js
- Then open up the dashboard at http://localhost:8042 and navigate to the Services section.
- Click the "[show unstable]" link in the top right hand corner. "Twitter Example" should now be listed as an installable connector.
- Click the Install Connector button.
The connector should now install and take you to the auth page. Follow the instructions from there.
NOTE: You might need to hit the two arrow "pop out" button to get the twitter auth process to work correctly!
You've made your first connector!