libereco
Version:
Liberate your photos from the hosting platforms lockin.
267 lines (207 loc) • 6.99 kB
JavaScript
var path = require('path')
, util = require('util')
, async = require('async')
, app = require('tako')() // it uses socket.io internaly
// apis
, APIs =
{
'500px' : require('./lib/api_500px'),
'flickr' : require('./lib/api_flickr')
}
// globals
, activeClients = 0
, oauthPool = {}
// settings
, Config =
{
port : 8000,
path : 'static',
index : 'index.html',
oauthCallback: '/oauth/callback'
}
;
// {{{ prepare environment
// process config settings
Config.host = getConfigVar('host');
Config.port = getConfigVar('port') || Config.port;
Config.path = getConfigVar('path') || Config.path;
if (Config.path[0] != '/') Config.path = path.join(__dirname, Config.path);
Config.index = getConfigVar('index') || Config.index;
if (Config.index[0] != '/') Config.index = path.join(Config.path, Config.index);
Config.oauthCallback = getConfigVar('oauth_callback') || Config.oauthCallback;
// check APIs
for (var service in APIs)
{
if (!APIs.hasOwnProperty(service)) continue;
// check that we have both api key and secret for each service
if (getConfigVar('api_'+service+'_key') && getConfigVar('api_'+service+'_secret'))
{
APIs[service].oauth(
{
key : getConfigVar('api_'+service+'_key'),
secret : getConfigVar('api_'+service+'_secret'),
callback : Config.oauthCallback
});
}
else
{
delete APIs[service];
}
}
// it makes sense to work with at least two services
if (Object.keys(APIs).length < 2)
{
console.error('Error: Please enable at least two api services.');
process.exit(0);
}
// socket.io settings
app.socketioManager.set('log level', 1);
app.socketioManager.set('transports', ['websocket']);
app.socketioManager.set('heartbeat interval', 20);
app.socketioManager.set('heartbeat timeout', 60);
// }}}
// {{{ define routing
// oauth callback
app.route(Config.oauthCallback, function appRouteOauthCallback(req, res)
{
var token = req.qs.oauth_token
, verifier = req.qs.oauth_verifier;
if (typeof oauthPool[token] == 'function')
{
oauthPool[token](verifier);
}
res.end('<script>window.close();</script>');
});
// static files + landing page
app.route('/').files(Config.index);
app.route('*').files(Config.path);
// sockets
app.sockets.on('connection', function socketsConnection(socket)
{
// let's party
socket.emit('ready');
activeClients++;
var browser = (socket.manager.handshaken[socket.id].headers['user-agent'].match(/(Chrome|Safari|Firefox|MSIE)(\/| )[0-9]+(\.[0-9]+)?/) || [socket.manager.handshaken[socket.id].headers['user-agent']])[0];
console.log(['connected', browser, socket.id, activeClients]);
socket.browser = browser;
// {{{ disconnect
socket.on('disconnect', function()
{
activeClients--;
console.log(['left', browser, socket.id, activeClients]);
});
// }}}
// {{{ service auth
// TODO: Maybe make it as handler for situations when access token is missing
socket.on('auth:request', function socketAuthRequestHandler(service, callback)
{
if (!(service in APIs)) return callback({status: 'error', message: 'Requested service ['+service+'] is not enabled.'});
// create api instance
var api = new APIs[service]();
// set current host
// TODO: Make it sane
api.set({callbackHost: socket.manager.handshaken[socket.id].headers.host });
// and store it in the socket
socket.set('api_'+service, api, function socketStoreApi()
{
// request auth token
api.authRequest(function apiAuthRequest(err, token, secret, results)
{
if (err) return callback({status: 'error', message: 'Unable to authenticate with the requested service ['+service+']'});
// wait for the callback
createOAuthVerifier(socket, service, token, secret);
callback({ status: 'ok', data: {token: token, secret: secret, results: results} });
});
});
});
// }}}
// {{{ fetch photos
socket.on('photos:fetch', function socketPhotosFetchHandler(service, page)
{
socket.get('api_'+service, function(err, api)
{
if (err) return console.error(['Unable to find API ['+service+'] data', err]);
// fetch photos
api.fetchPhotos(page, function(err, data)
{
if (err) return console.error(['Cant get photos from '+service, err]);
// return list of photos
socket.emit('photos:add', service, {page: page, photos: data});
});
});
});
// }}}
// {{{ upload photo
socket.on('upload:photo', function socketUploadPhotoHandler(data, callback)
{
// get two (from & to) apis
async.parallel(
{
from: function socketUploadPhotoGetApiFrom(cb)
{
socket.get('api_'+data.from, cb);
},
to: function socketUploadPhotoGetApiTo(cb)
{
socket.get('api_'+data.to, cb);
}
},
function socketUploadPhotoGetApiResult(err, apis)
{
// get photo info to upload
apis.from.getPhotoInfo(data.photo.id, function(err, photo)
{
if (err) return callback({status: 'error', message: err});
// upload photo to the destination api
// TODO: Streams would play here better,
// but wait for them to be stable
apis.to.uploadPhoto(photo, function(err, result)
{
if (err) return callback({status: 'error', message: err});
callback({status: 'ok', data: result});
});
});
});
});
// }}}
});
// }}}
// {{{ start server
app.httpServer.listen(Config.port, Config.host);
console.log('Listening on '+Config.host+':'+Config.port);
// }}}
/*
* subroutines
*/
// creates oauth verification handler,
// upon receiving callback from the api
// requests access token
function createOAuthVerifier(socket, service, token, secret)
{
oauthPool[token] = function oauthVerificationCallbackHandler(verifier)
{
socket.get('api_'+service, function(err, api)
{
if (err || !api) return console.error(['Cannot store access data ['+service+'] in the socket', (err ? err : 'api is null'), api]);
// get Access Token from the API
api.getAccessToken(token, secret, verifier, function oauthAccessTokenHandler(err, access_token, access_secret, results)
{
if (err) return console.error(['Cannot get access token for '+service, err]);
api.fetchUser(function(err, user)
{
if (err) return console.error(['Cannot get user data for '+service, err]);
socket.emit('auth:user', {service: service, user: api.Data.user});
});
});
});
// we done here, clean up
delete oauthPool[token];
};
}
// fetches variable from environment or npm config
// TODO: Should we account for 0?
function getConfigVar(key)
{
return process.env[key] || process.env['npm_package_config_'+key] || null;
}