Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

Commit

Permalink
feat($jsonpCallbacks): new service to abstract how JSONP callbacks ar…
Browse files Browse the repository at this point in the history
…e handled

You can now override this service if you have specific requirements about
the behaviour and formatting of the JSON_CALLBACK that is sent to the server
for `$http.jsonp` requests.

Closes #14795
  • Loading branch information
petebacondarwin committed Jun 21, 2016
1 parent 1a387ba commit a8cacfe
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 0 deletions.
1 change: 1 addition & 0 deletions angularFiles.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ var angularFiles = {
'src/ng/httpBackend.js',
'src/ng/interpolate.js',
'src/ng/interval.js',
'src/ng/jsonpCallbacks.js',
'src/ng/locale.js',
'src/ng/location.js',
'src/ng/log.js',
Expand Down
2 changes: 2 additions & 0 deletions src/AngularPublic.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
$HttpParamSerializerJQLikeProvider,
$HttpBackendProvider,
$xhrFactoryProvider,
$jsonpCallbacksProvider,
$LocationProvider,
$LogProvider,
$ParseProvider,
Expand Down Expand Up @@ -237,6 +238,7 @@ function publishExternalAPI(angular) {
$httpParamSerializerJQLike: $HttpParamSerializerJQLikeProvider,
$httpBackend: $HttpBackendProvider,
$xhrFactory: $xhrFactoryProvider,
$jsonpCallbacks: $jsonpCallbacksProvider,
$location: $LocationProvider,
$log: $LogProvider,
$parse: $ParseProvider,
Expand Down
83 changes: 83 additions & 0 deletions src/ng/jsonpCallbacks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
'use strict';

/**
* @ngdoc service
* @name $jsonpCallbacks
* @requires $window
* @description
* This service handles the lifecycle of callbacks to handle JSONP requests.
* Override this service if you wish to customise where the callbacks are stored and
* how they vary compared to the requested url.
*/
var $jsonpCallbacksProvider = function() {
this.$get = ['$window', function($window) {
var counter = 0;
$window.angular.callbacks = {};
var callbackMap = {};

function createCallback(callbackId) {
var callback = function(data) {
callback.data = data;
callback.called = true;
};
callback.id = callbackId;
return callback;
}

return {
/**
* @ngdoc method
* @name $jsonpCallbacks#createCallback
* @param {string} url the url of the JSONP request
* @returns {string} the callback path to send to the server as part of the JSONP request
* @description
* {@link $httpBackend} calls this method to create a callback and get hold of the path to the callback
* to pass to the server, which will be used to call the callback with its payload in the JSONP response.
*/
createCallback: function(url) {
var callbackId = '_' + (counter++).toString(36);
var callbackPath = 'angular.callbacks.' + callbackId;
var callback = createCallback(callbackId);
callbackMap[callbackPath] = $window.angular.callbacks[callbackId] = callback;
return callbackPath;
},
/**
* @ngdoc method
* @name $jsonpCallbacks#wasCalled
* @param {string} callbackPath the path to the callback that was sent in the JSONP request
* @returns {boolean} whether the callback has been called, as a result of the JSONP response
* @description
* {@link $httpBackend} calls this method to find out whether the JSONP response actually called the
* callback that was passed in the request.
*/
wasCalled: function(callbackPath) {
return callbackMap[callbackPath].called;
},
/**
* @ngdoc method
* @name $jsonpCallbacks#getResponse
* @param {string} callbackPath the path to the callback that was sent in the JSONP request
* @returns {*} the data received from the response via the registered callback
* @description
* {@link $httpBackend} calls this method to get hold of the data that was provided to the callback
* in the JSONP response.
*/
getResponse: function(callbackPath) {
return callbackMap[callbackPath].data;
},
/**
* @ngdoc method
* @name $jsonpCallbacks#removeCallback
* @param {string} callbackPath the path to the callback that was sent in the JSONP request
* @description
* {@link $httpBackend} calls this method to remove the callback after the JSONP request has
* completed or timed-out.
*/
removeCallback: function(callbackPath) {
var callback = callbackMap[callbackPath];
delete $window.angular.callbacks[callback.id];
delete callbackMap[callbackPath];
}
};
}];
};
73 changes: 73 additions & 0 deletions test/ng/jsonpCallbacksSpec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
'use strict';

describe('$jsonpCallbacks', function() {

beforeEach(module(function($provide) {
// mock out the $window object
$provide.value('$window', { angular: {} });
}));

describe('createCallback(url)', function() {

it('should return a new unique path to a callback function on each call', inject(function($jsonpCallbacks) {
var path = $jsonpCallbacks.createCallback('http://some.dummy.com/jsonp/request');
expect(path).toEqual('angular.callbacks._0');

path = $jsonpCallbacks.createCallback('http://some.dummy.com/jsonp/request');
expect(path).toEqual('angular.callbacks._1');

path = $jsonpCallbacks.createCallback('http://some.dummy.com/jsonp/request');
expect(path).toEqual('angular.callbacks._2');

path = $jsonpCallbacks.createCallback('http://some.dummy.com/jsonp/request');
expect(path).toEqual('angular.callbacks._3');
}));

it('should add a callback method to the $window.angular.callbacks collection on each call', inject(function($window, $jsonpCallbacks) {
$jsonpCallbacks.createCallback('http://some.dummy.com/jsonp/request');
expect($window.angular.callbacks._0).toEqual(jasmine.any(Function));

$jsonpCallbacks.createCallback('http://some.dummy.com/jsonp/request');
expect($window.angular.callbacks._1).toEqual(jasmine.any(Function));

$jsonpCallbacks.createCallback('http://some.dummy.com/jsonp/request');
expect($window.angular.callbacks._2).toEqual(jasmine.any(Function));

$jsonpCallbacks.createCallback('http://some.dummy.com/jsonp/request');
expect($window.angular.callbacks._3).toEqual(jasmine.any(Function));
}));
});


describe('wasCalled(callbackPath)', function() {

it('should return true once the callback has been called', inject(function($window, $jsonpCallbacks) {
var path = $jsonpCallbacks.createCallback('http://some.dummy.com/jsonp/request');
expect($jsonpCallbacks.wasCalled(path)).toBeFalsy();
var response = {};
$window.angular.callbacks._0(response);
expect($jsonpCallbacks.wasCalled(path)).toBeTruthy();
}));
});


describe('getResponse(callbackPath)', function() {

it('should retrieve the data from when the callback was called', inject(function($window, $jsonpCallbacks) {
var path = $jsonpCallbacks.createCallback('http://some.dummy.com/jsonp/request');
var response = {};
$window.angular.callbacks._0(response);
var result = $jsonpCallbacks.getResponse(path);
expect(result).toBe(response);
}));
});

describe('removeCallback(calbackPath)', function() {

it('should remove the callback', inject(function($window, $jsonpCallbacks) {
var path = $jsonpCallbacks.createCallback('http://some.dummy.com/jsonp/request');
$jsonpCallbacks.removeCallback(path);
expect($window.angular.callbacks._0).toBeUndefined();
}));
});
});

0 comments on commit a8cacfe

Please sign in to comment.