hello.js
A client-side JavaScript SDK for authenticating with OAuth2 (and OAuth1 with a oauth proxy) web services and querying their REST APIs. HelloJS standardizes paths and responses to common APIs like Google Data Services, Facebook Graph and Windows Live Connect. It's modular, so that list is growing. No more spaghetti code!
Features
Here are some more demos...
- Items marked with a ✓ are fully working and can be tested here.
- Items marked with a ✗ aren't provided by the provider at this time.
- Blank items are a work in progress, but there is good evidence that they can be done.
- I have no knowledge of anything unlisted and would appreciate input.
Install
Download: HelloJS | HelloJS (minified)
Compiled source, which combines all of the modules, can be obtained from GitHub, and source files can be found in Source.
Bower Package
npm install bower
bower install hello
The Bower package shall install the aforementioned "/src" and "/dist" directories. The "/src" directory provides individual modules which can be packaged as desired.
Note: Some services require OAuth1 or server-side OAuth2 authorization. In such cases, HelloJS communicates with an OAuth Proxy.
Help & Support
Quick Start
Quick start shows you how to go from zero to loading in the name and picture of a user, like in the demo above.
1. Register
Register your application with at least one of the following networks. Ensure you register the correct domain as they can be quite picky.
2. Include Hello.js script in your page
<script class="pre" src="./dist/hello.all.js"></script>
3. Create the sign-in buttons
Just add onclick events to call hello(network).login(). Style your buttons as you like; I've used zocial css, but there are many other icon sets and fonts.
<button onclick="hello('windows').login()">windows</button>
4. Add listeners for the user login
Let's define a simple function, which will load a user profile into the page after they sign in and on subsequent page refreshes. Below is our event listener which will listen for a change in the authentication event and make an API call for data.
hello.on('auth.login', function(auth) {
hello(auth.network).api('/me').then(function(r) {
var label = document.getElementById('profile_' + auth.network);
if (!label) {
label = document.createElement('div');
label.id = 'profile_' + auth.network;
document.getElementById('profile').appendChild(label);
}
label.innerHTML = '<img src="' + r.thumbnail + '" /> Hey ' + r.name;
});
});
5. Configure hello.js with your client IDs and initiate all listeners
Now let's wire it up with our registration detail obtained in step 1. By passing a [key:value, ...] list into the hello.init
function. e.g....
hello.init({
facebook: FACEBOOK_CLIENT_ID,
windows: WINDOWS_CLIENT_ID,
google: GOOGLE_CLIENT_ID
}, {redirect_uri: 'redirect.html'});
That's it. The code above actually powers the demo at the start so, no excuses.
Core Methods
hello.init()
Initiate the environment. And add the application credentials.
hello.init({facebook: id, windows: id, google: id, ... })
name | type |
---|
credentials | object( key => value, ... )
name | type | example | description | argument | default |
---|
key | string | windows , facebook or google | App names | required | n/a | value | string | 0000000AB1234 | ID of the service to connect to | required | n/a |
|
options | sets default options, as in hello.login() |
Example:
hello.init({
facebook: '359288236870',
windows: '000000004403AD10'
});
hello.login()
If a network string is provided: A consent window to authenticate with that network will be initiated. Else if no network is provided a prompt to select one of the networks will open. A callback will be executed if the user authenticates and or cancels the authentication flow.
hello.login([network] [, options] [, callback()])
name | type | example | description | argument | default |
---|
network | string | windows , facebook | One of our services. | required | null |
options | object
name | type | example | description | argument | default |
---|
display | string | popup , page or none | "popup" - as the name suggests, "page" - navigates the whole page, "none" - refresh the access_token in the background | optional | popup | scope | string | email , publish or photos | Comma separated list of scopes | optional | null | redirect_uri | string | redirect.html |
A full or relative URI of a page which includes this script file hello.js |
optional |
window.location.href | response_type | string | token , code | Implicit (token) or Explicit (code) Grant flow | optional | token | force | Boolean or null | true, false or null | (true) initiate auth flow and prompt for reauthentication where available. (null) initiate auth flow. (false) only prompt auth flow if the scopes have changed or the token expired. | optional | null | popup | object | {resizable:1} | Overrides the popups specs | optional | See hello.settings.popup |
|
callback | function | function(){alert("Logged in!");} | A callback when the users session has been initiated | optional | null |
Examples:
hello('facebook').login().then(function() {
alert('You are signed in to Facebook');
}, function(e) {
alert('Signin error: ' + e.error.message);
});
hello.logout()
Remove all sessions or individual sessions.
hello.logout([network] [, options] [, callback()])
name | type | example | description | argument | default |
---|
network | string |
windows ,
facebook
| One of our services. |
optional
|
null
|
options | object
name | type | example | description | argument | default |
---|
force | boolean | true | If set to true, the user will be logged out of the providers site as well as the local application. By default the user will still be signed into the providers site. |
optional
| false |
|
callback | function |
function() {alert('Logged out!');}
|
A callback when the users session has been terminated |
optional
|
null
|
Example:
hello('facebook').logout().then(function() {
alert('Signed out');
}, function(e) {
alert('Signed out error: ' + e.error.message);
});
hello.getAuthResponse()
Get the current status of the session. This is a synchronous request and does not validate any session cookies which may have expired.
hello.getAuthResponse(network)
name | type | example | description | argument | default |
---|
network | string | windows , facebook | One of our services. | optional | current |
Examples:
var online = function(session) {
var currentTime = (new Date()).getTime() / 1000;
return session && session.access_token && session.expires > currentTime;
};
var fb = hello('facebook').getAuthResponse();
var wl = hello('windows').getAuthResponse();
alert((online(fb) ? 'Signed' : 'Not signed') + ' into Facebook, ' + (online(wl) ? 'Signed' : 'Not signed') + ' into Windows Live');
hello.api()
Make calls to the API for getting and posting data.
hello.api([path], [method], [data], [callback(json)])
hello.api([path], [method], [data], [callback(json)]).then(successHandler, errorHandler)
.
<tr>
<td colspan=6>More options (below) require putting the options into a 'key'=>'value' hash. I.e. <code>hello(network).api(options)</code></td>
</tr>
<tr>
<td>timeout</td>
<td><i>integer</i></td>
<td>
<code>3000</code> = 3 seconds.
</td>
<td>
Wait <em>milliseconds</em> before resolving the Promise with a reject.
</td>
<td>
<em>optional</em>
</td>
<td>
<em>60000</em>
</td>
</tr>
<tr>
<td>formatResponse</td>
<td><i>boolean</i></td>
<td>
<code>false</code>
</td>
<td>
<code>true</code>: format the response, <code>false</code>: return raw response.
</td>
<td>
<em>optional</em>
</td>
<td>
<em>true</em>
</td>
</tr>
name | type | example | description | argument | default |
---|
path | string |
/me ,
/me/friends
| Path or URI of the resource. See REST API, Can be prefixed with the name of the service. |
required
| null |
method |
get ,
post ,
delete ,
put
| See
type
| HTTP request method to use. |
optional
|
get
|
data | object |
{name:Hello , description:Fandelicious }
|
A JSON object of data, FormData, HTMLInputElement, HTMLFormElment to be sent along with a
get ,
post or
put request
|
optional
|
null
|
callback | function |
function(json){console.log(json);}
|
A function to call with the body of the response returned in the first parameter as an object, else boolean false.
|
optional
|
null
|
Examples:
hello('facebook').api('me').then(function(json) {
alert('Your name is ' + json.name);
}, function(e) {
alert('Whoops! ' + e.error.message);
});
Event Subscription
Please see demo of the global events.
hello.on()
Bind a callback to an event. An event may be triggered by a change in user state or a change in some detail.
hello.on(event, callback)
event | description |
---|
auth | Triggered whenever session changes |
auth.login | Triggered whenever a user logs in |
auth.logout | Triggered whenever a user logs out |
auth.update | Triggered whenever a users credentials change |
Example:
var sessionStart = function() {
alert('Session has started');
};
hello.on('auth.login', sessionStart);
hello.off()
Remove a callback. Both event name and function must exist.
hello.off(event, callback)
hello.off('auth.login', sessionStart);
Misc
Pagination, Limit and Next Page
Responses which are a subset of the total results should provide a response.paging.next
property. This can be plugged back into hello.api
in order to get the next page of results.
In the example below the function paginationExample()
is initially called with me/friends
. Subsequent calls take the path from resp.paging.next
.
function paginationExample(path) {
hello('facebook')
.api(path, {limit: 1})
.then(
function callback(resp) {
if (resp.paging && resp.paging.next) {
if (confirm('Got friend ' + resp.data[0].name + '. Get another?')) {
paginationExample(resp.paging.next);
}
}
else {
alert('Got friend ' + resp.data[0].name);
}
},
function() {
alert('Whoops!');
}
);
}
paginationExample('me/friends');
Scope
The scope property defines which privileges an app requires from a network provider. The scope can be defined globally for a session through hello.init(object, {scope: 'string'})
, or at the point of triggering the auth flow e.g. hello('network').login({scope: 'string'});
An app can specify multiple scopes, separated by commas - as in the example below.
hello('facebook').login({scope: 'friends,photos,publish'});
Scopes are tightly coupled with API requests, which will break if the session scope is missing or invalid. The best way to see this is next to the API paths in the hello.api reference table.
The table below illustrates some of the default scopes HelloJS exposes. Additional scopes may be added which are proprietary to a service, but be careful not to mix proprietary scopes with other services which don't know how to handle them.
Scope | Description |
---|
default | Read basic profile |
---|
friends | Read friends profiles |
---|
photos | Read users albums and photos |
---|
files | Read users files |
---|
publish | Publish status updates |
---|
publish_files | Publish photos and files |
---|
It's good practice to limit the use of scopes and also to make users aware of why your app needs certain privileges. Try to update the permissions as a user delves further into your app. For example: If the user would like to share a link with a friend, include a button that the user has to click to trigger the hello.login with the 'friends' scope, and then the handler triggers the API call after authorization.
Error Handling
Errors are returned i.e. hello.api([path]).then(null, [*errorHandler*])
- alternatively hello.api([path], [*handleSuccessOrError*])
.
The Promise response standardizes the binding of error handlers.
Error Object
The first parameter of a failed request to the errorHandler may be either boolean (false) or be an Error Object...
name | type |
---|
error | object
name | type | example | description | argument | default |
---|
code | string |
request_token_unauthorized
| Code |
required
| n/a | message | string | The provided access token.... |
Error message |
required
| n/a |
|
Extending the services
Services are added to HelloJS as "modules" for more information about creating your own modules and examples, go to Modules
OAuth Proxy
A list of the service providers OAuth* mechanisms is available at Provider OAuth Mechanisms
For providers which support only OAuth1 or OAuth2 with Explicit Grant, the authentication flow needs to be signed with a secret key that may not be exposed in the browser. HelloJS gets round this problem by the use of an intermediary webservice defined by oauth_proxy
. This service looks up the secret from a database and performs the handshake required to provision an access_token
. In the case of OAuth1, the webservice also signs subsequent API requests.
Quick start: Register your Client ID and secret at the OAuth Proxy service, Register your App
The default proxy service is https://auth-server.herokuapp.com/. Developers may add their own network registration Client ID and secret to this service in order to get up and running.
Alternatively recreate this service with node-oauth-shim. Then override the default oauth_proxy
in HelloJS client script in hello.init
, like so...
hello.init(
CLIENT_IDS,
{
oauth_proxy: 'https://auth-server.herokuapp.com/proxy'
}
)
Enforce Explicit Grant
Enforcing the OAuth2 Explicit Grant is done by setting response_type=code
in hello.login options - or globally in hello.init options. E.g...
hello(network).login({
response_type: 'code'
});
Refresh Access Token
Access tokens provided by services are generally short lived - typically 1 hour. Some providers allow for the token to be refreshed in the background after expiry.
A list of services which enable silent authentication after the Implicit Grant signin Refresh access_token
Unlike Implicit grant; Explicit grant may return the refresh_token
. HelloJS honors the OAuth2 refresh_token, and will also request a new access_token once it has expired.
Bulletproof Requests
A good way to design your app is to trigger requests through a user action, you can then test for a valid access token prior to making the API request with a potentially expired token.
var google = hello('google');
google.login({force: false}).then(function() {
google.api('me').then(handler);
});
Promises A+
The response from the async methods hello.login
, hello.logout
and hello.api
return a thenable method which is Promise A+ compatible.
For a demo, or, if you're bundling up the library from src/*
files, then please checkout Promises
Browser Support
HelloJS targets all modern browsers.
Polyfills are included in src/hello.polyfill.js
this is to bring older browsers upto date. If you're using the resources located in dist/
this is already bundled in. But if you're building from source you might like to first determine whether these polyfills are required, or if you're already supporting them etc...
PhoneGap Support
HelloJS can also be run on PhoneGap applications. Checkout the demo hellojs-phonegap-demo
Thankyou
HelloJS relies on the these fantastic services for its development and deployment. Without which it would still be kicking around in a cave - not evolving very fast.
- BrowserStack for providing a means to test across multiple devices.
- Github for maintaining the repo and issue tracking.
- Travis for providing fantastic continuous integration.
- ... and others i've forgotten to mention
Can i contribute?
Yes, yes you can. Infact this isnt really free software, it comes with bugs documentation errors, more over it tracks thirdparty API's which just wont sit still. And its intended for everyone to understand, so if you dont understand something then its not fulfilling its goal.
... otherwise give it a star.
Changing Code?
Ensure you setup and test your code on a variety of browsers.
npm install -l
sudo npm install -g grunt-cli
grunt test
python -m SimpleHTTPServer