├── LICENSE
├── README.markdown
├── example
├── index.html
└── js
│ ├── example.js
│ └── jquery.tmpl.js
└── jquery.sc.api.js
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015 SoundCloud Ltd.
2 |
3 | Permission is hereby granted, free of charge, to any person
4 | obtaining a copy of this software and associated documentation
5 | files (the "Software"), to deal in the Software without
6 | restriction, including without limitation the rights to use,
7 | copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the
9 | Software is furnished to do so, subject to the following
10 | conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.markdown:
--------------------------------------------------------------------------------
1 | ## jQuery plugin: SoundCloud oAuth 2.0 API wrapper
2 |
3 | ### A simple usage example
4 |
5 | First and most important, you'll need to get a Client ID from SoundCloud. If
6 | you haven't got one already, just register an app on the SoundCloud [Apps][] page.
7 |
8 | [apps]: http://soundcloud.com/you/apps/new
9 |
10 | Include the plugin in your HTML code:
11 |
12 | ```html
13 |
14 | ```
15 |
16 | and then, initialize it:
17 |
18 | ```javascript
19 | var api = $.sc.api('Enter your Client ID here');
20 | ```
21 |
22 | or handle the successful authorization yourself:
23 |
24 | ```javascript
25 | var api = $.sc.api('Enter your Client ID here', {
26 | onAuthSuccess: function(user, container) {
27 | alert('you are SoundCloud user ' + user.username);
28 | }
29 | });
30 | ```
31 |
32 | also instead of passing the callbacks you can use the custom events:
33 |
34 | ```javascript
35 | var api = $.sc.api('Enter your Client ID here');
36 | $(document).bind($.sc.api.events.AuthSuccess, function(event) {
37 | var user = event.user;
38 | // do something with the user object or call the api
39 | api.get('/me/tracks', function(tracks) {
40 | console.log(tracks);
41 | })
42 | });
43 | ```
44 |
45 | Please refer to the [wiki][] for full documentation.
46 |
47 | [wiki]: https://github.com/soundcloud/SoundCloud-API-jQuery-plugin/wiki
48 |
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
19 |
20 |
--------------------------------------------------------------------------------
/example/js/example.js:
--------------------------------------------------------------------------------
1 | (function($) {
2 | var soundCloudApiKey = 'Enter your Client ID here';
3 | var api = $.sc.api(soundCloudApiKey, {
4 | onAuthSuccess: function(user, container) {
5 | $('Logged in as: ' + user.username + '').prependTo(container);
6 | console.log('you are SoundCloud user ' + user.username);
7 | }
8 | });
9 |
10 | // wait for the API to be available
11 | $(document).bind($.sc.api.events.AuthSuccess, function(event) {
12 | var user = event.user;
13 | // call the api
14 | api.get('/me/tracks', function(data) {
15 | console.log('and here are your tracks', data);
16 | // you can use new jQuery templating for generating the track list
17 | $('#track').render(data).appendTo("#track-list");
18 | });
19 | });
20 |
21 |
22 | })(jQuery);
23 |
--------------------------------------------------------------------------------
/example/js/jquery.tmpl.js:
--------------------------------------------------------------------------------
1 | /*
2 | * jQuery Templating Plugin
3 | * NOTE: Created for demonstration purposes.
4 | * Copyright 2010, John Resig
5 | * Dual licensed under the MIT or GPL Version 2 licenses.
6 | */
7 | (function(jQuery){
8 | // Override the DOM manipulation function
9 | var oldManip = jQuery.fn.domManip;
10 |
11 | jQuery.fn.extend({
12 | render: function( data ) {
13 | return this.map(function(i, tmpl){
14 | return jQuery.render( tmpl, data );
15 | });
16 | },
17 |
18 | // This will allow us to do: .append( "template", dataObject )
19 | domManip: function( args ) {
20 | // This appears to be a bug in the appendTo, etc. implementation
21 | // it should be doing .call() instead of .apply(). See #6227
22 | if ( args.length > 1 && args[0].nodeType ) {
23 | arguments[0] = [ jQuery.makeArray(args) ];
24 | }
25 |
26 | if ( args.length === 2 && typeof args[0] === "string" && typeof args[1] !== "string" ) {
27 | arguments[0] = [ jQuery.render( args[0], args[1] ) ];
28 | }
29 |
30 | return oldManip.apply( this, arguments );
31 | }
32 | });
33 |
34 | jQuery.extend({
35 | render: function( tmpl, data ) {
36 | var fn;
37 |
38 | // Use a pre-defined template, if available
39 | if ( jQuery.templates[ tmpl ] ) {
40 | fn = jQuery.templates[ tmpl ];
41 |
42 | // We're pulling from a script node
43 | } else if ( tmpl.nodeType ) {
44 | var node = tmpl, elemData = jQuery.data( node );
45 | fn = elemData.tmpl || jQuery.tmpl( node.innerHTML );
46 | }
47 |
48 | fn = fn || jQuery.tmpl( tmpl );
49 |
50 | // We assume that if the template string is being passed directly
51 | // in the user doesn't want it cached. They can stick it in
52 | // jQuery.templates to cache it.
53 |
54 | if ( jQuery.isArray( data ) ) {
55 | return jQuery.map( data, function( data, i ) {
56 | return fn.call( data, jQuery, data, i );
57 | });
58 |
59 | } else {
60 | return fn.call( data, jQuery, data, 0 );
61 | }
62 | },
63 |
64 | // You can stick pre-built template functions here
65 | templates: {},
66 |
67 | /*
68 | * For example, someone could do:
69 | * jQuery.templates.foo = jQuery.tmpl("some long templating string");
70 | * $("#test").append("foo", data);
71 | */
72 |
73 | tmplcmd: {
74 | "each": {
75 | _default: [ null, "$i" ],
76 | prefix: "jQuery.each($1,function($2){with(this){",
77 | suffix: "}});"
78 | },
79 | "if": {
80 | prefix: "if($1){",
81 | suffix: "}"
82 | },
83 | "else": {
84 | prefix: "}else{"
85 | },
86 | "html": {
87 | prefix: "_.push(typeof $1==='function'?$1.call(this):$1);"
88 | },
89 | "=": {
90 | _default: [ "this" ],
91 | prefix: "_.push($.encode(typeof $1==='function'?$1.call(this):$1));"
92 | }
93 | },
94 |
95 | encode: function( text ) {
96 | return text != null ? document.createTextNode( text.toString() ).nodeValue : "";
97 | },
98 |
99 | tmpl: function(str, data, i) {
100 | // Generate a reusable function that will serve as a template
101 | // generator (and which will be cached).
102 | var fn = new Function("jQuery","$data","$i",
103 | "var $=jQuery,_=[];_.data=$data;_.index=$i;" +
104 |
105 | // Introduce the data as local variables using with(){}
106 | "with($data){_.push('" +
107 |
108 | // Convert the template into pure JavaScript
109 | str
110 | .replace(/[\r\t\n]/g, " ")
111 | .replace(/\${([^}]*)}/g, "{{= $1}}")
112 | .replace(/{{(\/?)(\w+|.)(?:\((.*?)\))?(?: (.*?))?}}/g, function(all, slash, type, fnargs, args) {
113 | var tmpl = jQuery.tmplcmd[ type ];
114 |
115 | if ( !tmpl ) {
116 | throw "Template not found: " + type;
117 | }
118 |
119 | var def = tmpl._default;
120 |
121 | return "');" + tmpl[slash ? "suffix" : "prefix"]
122 | .split("$1").join(args || def[0])
123 | .split("$2").join(fnargs || def[1]) + "_.push('";
124 | })
125 | + "');}return $(_.join('')).get();");
126 |
127 | // Provide some basic currying to the user
128 | return data ? fn.call( this, jQuery, data, i ) : fn;
129 | }
130 | });
131 | })(jQuery);
132 |
--------------------------------------------------------------------------------
/jquery.sc.api.js:
--------------------------------------------------------------------------------
1 | /*
2 | * SoundCloud API wrapper for jQuery using oAuth2
3 | * Author: Matas Petrikas, matas@soundcloud.com
4 | * Copyright (c) 2010 SoundCloud Ltd.
5 | * Licensed under the MIT license:
6 | * http://www.opensource.org/licenses/mit-license.php
7 | */
8 |
9 | // TODOs
10 | // implement HTML5 uplaod flow
11 | // encrypt the security token in the localStorage
12 |
13 | (function($) {
14 | $.sc = $.sc || {};
15 |
16 | // api wrapper event dictionary
17 | var events = {
18 | AuthSuccess : 'onScAuth',
19 | AuthDenied : 'onScAuthDenied',
20 | AuthError : 'onScAuthError',
21 | NonAuth : 'onScNotAuth',
22 | ApiCall : 'onScApiCall',
23 | ApiError : 'onScApiError',
24 | UnSupported: 'onScApiUnsupported'
25 | };
26 |
27 | $.sc.api = function(apiKey, callerSettings) {
28 | var settings = $.extend({
29 | debug: true, // if enabled, will print most errors into browser console
30 | useSandbox: false, // set it to true, if you're working on your code, revert to false for live deploy
31 | host: this.useSandbox ? 'sandbox-soundcloud.com' : 'soundcloud.com',
32 | redirect: window.location.href.replace(/(\?|#).*$/, ''), // redirect after authorization, default is the current page
33 | container: $('').prependTo(document.body), // the DOM node where the Auth status is displayed
34 | authRequired: true, // set to false if you need only public resources
35 | reAuthorize: true, // if true, wil try to re-authorize on expired token
36 | onAuthSuccess: function(user, container) { // called when valid token is detected
37 | $('Logged in as: ' + user.username + '').prependTo(container);
38 | },
39 | onAuthDeny: function(error) { // called when user denied the authorization
40 | },
41 | onNonAuth: function(connectUrl, container) { // called when no valid token is found
42 | $('', {
43 | html: 'Connect to SoundCloud',
44 | href: connectUrl,
45 | 'class': 'sc-authorize'
46 | })
47 | .prependTo(container);
48 | },
49 | onApiError: function(xhr, status, error) { // called when API returns an error
50 | }
51 | }, callerSettings || {}),
52 | log = function(args) {
53 | if(settings.debug && console && 'log' in console){
54 | console.log.apply(console, arguments);
55 | }
56 | },
57 | // trigger custom events on certain methods
58 | anounceEvent = function(eventType, obj) {
59 | $(document).trigger({type: eventType, scObj : obj});
60 | },
61 | // utility for parsing params from query strings
62 | getParamInQuery = function(path, param) {
63 | var match = path.match(new RegExp("(?:\\?|&|#)" + param + "=([^&]*)(?:&|$)"));
64 | if (match) {
65 | return decodeURIComponent(match[1]);
66 | }
67 | },
68 | // API authorization token
69 | token,
70 | tokenId = 'scAuth',
71 | removeToken = function() {
72 | // remove the token from the client
73 | localStorage.removeItem(tokenId);
74 | },
75 | // reading token from the location.hash or localStorage
76 | readToken = function() {
77 | var rToken = getParamInQuery(window.location.hash, 'access_token'),
78 | error = getParamInQuery(window.location.href, 'error'),
79 | now = new Date().getTime(),
80 | // for now, token is valid for one hour
81 | tokenLife = 3600,
82 | ls = window.localStorage,
83 | tokenObj;
84 | // in case we're returning from the SoundCloud connect authorization page, store the token in the localStorage
85 | if(rToken){
86 | // clear the token from the hash
87 | window.location.hash = '';
88 | // store token
89 | ls.setItem(tokenId, JSON.stringify({token: rToken, host: settings.host, date: now}));
90 | }else if(error && error === 'user_denied'){
91 | // remove some eventual old token
92 | removeToken();
93 | // if user denied access, let the DOM know about it
94 | log('getting token: user denied', arguments);
95 | settings.onAuthDeny(error);
96 | anounceEvent(events.AuthDenied, {host: settings.host});
97 | }else if(error && error === 'invalid_client'){
98 | log('getting token: wrong API key', arguments);
99 | throw "API key error, please make sure the key you are using is valid!";
100 | }
101 | // TODO potentialy some other errors could be handled here too
102 | // read the token from the localStorage
103 | tokenObj = JSON.parse(localStorage.getItem(tokenId));
104 | // check if the token is issued for the correct host
105 | // check the existing token time expiry
106 | if(tokenObj && tokenObj.token && tokenObj.host === settings.host && now - tokenObj.date < tokenLife * 1000){
107 | return tokenObj.token;
108 | }else{
109 | return undefined;
110 | }
111 | },
112 | logout = function() {
113 | removeToken();
114 | // reload the page without any tokens
115 | window.location = window.location.href.replace(/(#).*$/, '');
116 | },
117 | connectUrl = 'https://' + settings.host + '/connect?client_id=' + apiKey +'&response_type=token&redirect_uri=' + settings.redirect,
118 | authorize = function() {
119 | window.location = connectUrl;
120 | },
121 | reqHeaders = function (xhr) {
122 | if(settings.authRequired){
123 | xhr.setRequestHeader('Authorization', "OAuth " + token);
124 | }
125 | xhr.setRequestHeader('Accept', "application/json");
126 | },
127 | // call the SoundCloud API, supported signatures: callApi(resource, callback) or callApi(resource, paramsObj, callback)
128 | callApi = function(params) {
129 | var onError = function(xhr, status, error) {
130 | log('callApi: API error', arguments);
131 | settings.onApiError(xhr, status, error);
132 | anounceEvent(events.ApiError, {resource: params.resource, xhr: xhr, status: status, error: error});
133 | // if the token expired, try to reauthorize automatically
134 | if(settings.reAuthorize){
135 | authorize();
136 | }
137 | };
138 |
139 | // throw an error if calling api without token and auth needed
140 | if(settings.authRequired && !token){
141 | throw "Please authorize before calling the API at " + settings.host;
142 | }
143 |
144 | return $.ajax({
145 | url: 'https://api.' + settings.host + params.resource,
146 | beforeSend: reqHeaders,
147 | data: params.data || {},
148 | success: function(data, textStatus, xhr) {
149 | // check if data is returned, in FF 401 would still trigger success
150 | if(!data){
151 | onError(xhr, 'Authorization Unsuccessful, client-side catch', 401);
152 | return;
153 | }
154 | if($.isFunction(params.callback)){
155 | params.callback(data);
156 | }
157 | anounceEvent(events.ApiCall, {resource: params.resource, data: data});
158 | },
159 | error: onError
160 | });
161 | },
162 | // handle multiple method signatures, jQuery style
163 | prepareCallObj = function(args, method) {
164 | var obj = {resource: args[0], method: method};
165 | if(args.length === 3){
166 | obj.callback = args[2];
167 | obj.data = args[1];
168 | }else{
169 | obj.callback = $.isFunction(args[1]) ? args[1] : null;
170 | obj.data = $.isFunction(args[1]) ? {} : args[1];
171 | }
172 | return obj;
173 | },
174 | // generate API methods
175 | callMethod = function(method) {
176 | return function(resource, data, callback) {
177 | return callApi(prepareCallObj(arguments, method));
178 | };
179 | },
180 | // gets the currently authorized user
181 | getMe = function(callback) {
182 | callMethod('GET')('/me', callback);
183 | },
184 | // checks if the wrapper is supported on this client
185 | supported = true;
186 |
187 | // check if the browser supports the wrapper, check if we have a token available
188 | if((JSON && 'parse' in JSON) && (localStorage && 'getItem' in localStorage)){
189 | token = readToken();
190 | }else{
191 | supported = false;
192 | log('SoundCloud API: the browser is not currently supported');
193 | anounceEvent(events.UnSupported);
194 | }
195 |
196 | if(!apiKey){
197 | // check if the developer provided an api key
198 | throw "Please provide an API key for " + settings.host;
199 | }else if(token){
200 | // if already authorized, try to get the logged in user's data
201 | getMe(function(user) {
202 | settings.onAuthSuccess(user, settings.container);
203 | anounceEvent(events.AuthSuccess, {user: user});
204 | });
205 | }else{
206 | // if not yet authorized, execute default callback
207 | if(settings.authRequired){
208 | settings.onNonAuth(connectUrl, settings.container);
209 | }
210 | anounceEvent(events.NonAuth, {connectUrl: connectUrl});
211 | }
212 | // if the wrapper is functional, return the public interface
213 | return {
214 | isAuthorized: !!token,
215 | supported: supported,
216 | authorize: authorize,
217 | logout: logout,
218 | callApi: callApi,
219 | get: callMethod('GET'),
220 | post: callMethod('POST'),
221 | put: callMethod('PUT'),
222 | 'delete': callMethod('DELETE')
223 | };
224 | };
225 | // make events available publicly
226 | $.sc.api.events = events;
227 |
228 | })(jQuery);
--------------------------------------------------------------------------------