Jasmine AJAX mock turns string into array - ajax

I'm trying to write a test suite around my AjaxRequest class, but when I'm trying to inspect the request body I get this test failure
FAILED TESTS:
AjaxRequest
#POST
✖ attaches the body to the response
PhantomJS 1.9.8 (Mac OS X 0.0.0)
Expected Object({ example: [ 'text' ] }) to equal Object({ example: 'text' }).
Here's the relevant bit of the unit test:
req = new AjaxRequest().post('http://example.com')
.body({
example: 'text'
}).run();
And here's the run() method where the ajax request is made
var options = {
url: this._url,
method: this._method,
type: 'json',
data: this._body
};
return when(reqwest(options));
I'm using reqwest to issue ajax requests.
Could someone point out why it's expecting ['text'] when the request sent 'text' in the json body?
Thank you!

Changing the implementation of AjaxRequest solved the problem.
Here is the new implementation of run using XMLHttpRequest
run () {
var req = new XMLHttpRequest();
req.open(this._method, this._url, true);
req.send(JSON.stringify(this._body));
return when.promise((resolve, reject) => {
req.onload = function() {
if (req.status < 400) {
var param = req.response;
try { param = JSON.parse(param) } catch (e) { };
resolve(param);
} else {
reject(new RequestError(req.statusText, req.status));
}
};
});
}
This not only gets rid of an extra library but also leaves more control over when to reject a request promise.

Related

Fitbit URL callback giving a response of NULL

I'm having trouble getting a response from a callback uri and I would really appreciate any help you could give me.
I am trying to use the Fitbit API which requires you to use a callback url to get an Auth Code.
Workflow:
1. Go to Fitbit url to get user to allow the app access to their personal data.
2. User agrees to the conditions
3. User gets redirected to my API
4. The API returns the code from (Code is located in URL and I can access it)
5. I console.log the code out to verify it
6. API returns the code
7. I work with code then exchanging it for an access token.
The problem is that I don't return the code (Or anything )when I return to the app even though I can console.log it on the API. The response I get is NULL
Here is the URL:
url = "https://www.fitbit.com/oauth2/authorize?response_type=code&client_id=CLIENT_ID&redirect_uri=https://REDIRECT_URL&scope=activity%20heartrate%20location%20nutrition%20profile%20settings%20sleep%20social%20weight&expires_in=604800";
I then open the URL in the InAPPBrowser successfully:
if (url !== "") {
const canOpen = await Linking.canOpenURL(url)
if (canOpen) {
try {
const isAvailable = await InAppBrowser.isAvailable()
if (isAvailable) {
const result =InAppBrowser.open(url, {
// iOS Properties
dismissButtonStyle: 'done',
preferredBarTintColor: 'gray',
preferredControlTintColor: 'white',
// Android Properties
showTitle: true,
toolbarColor: '#6200EE',
secondaryToolbarColor: 'black',
enableDefaultShare: true,
}).then((result) => {
console.log("Response:",JSON.stringify(result))
Linking.getInitialURL().then(url => {
console.log("Tests: ",url)
this._setTracker(url as string);
});
})
} else Linking.openURL(url)
} catch (error) {
console.log("Error: ",error)
}
}
}
From here the URL opens successfully.
Here is the API now which is done in Typescript on AWS serverless and Lambda
export const handler: APIGatewayProxyHandler = async (event, _context, callback) =>{
let provider = event.path
//prints code
let x = event.queryStringParameters
console.log("Code: ",x)
const response = {
statusCode: 200,
body: "Success"
};
return response;
}
Please let me know if further detail is required?
Thank you!
Right so it turns out what I was doing was correct apart from the response should have been 301 which is a redirect response.
const response= {
statusCode: 301,
headers: {
"location": `app://CALLBACK RESPONSE ADDRESS?type=${provider}`
},
body: "Boom"
}

How do I blend a promise with an observable?

I'm having trouble promises and observables. I have a handful of http requests which are defined in a package using promises. In the rest of my code I am using observables for various things, including other http calls. In one particular section I am checking to see if the user's bearer token is expired and if so then I get a new token and then proceed with the rest of the call.
if (!token || token.exp < Math.round((new Date()).getTime() / 1000)) {
from(this._store.refreshBearerToken())
.pipe(flatMap(resp => {
let newToken = resp.data;
newToken.exp = (new Date()).getTime() / 1000 + newToken.expires_in;
localStorage.setItem('token', JSON.stringify(newToken))
options = options || {};
options.headers = new HttpHeaders({
"Authorization": `${newToken.token_type} ${newToken.access_token}`,
"Content-Type": "application/json"
});
return this._http$.request<T>(method, url, options as Object).pipe(share());
}));
}
Bearer Token method:
async refreshBearerToken() {
const response = await this._q2.sources.requestExtensionData({
route: "refreshBearerToken"
});
console.log(response);
return response;
}
Since this._store.refreshBearerToken returns a promise I wrapped the call in a from to convert it to an observable. This compiles but when it runs I get "Cannot read property 'pipe' of undefined".
How can I convert this promise to an observable so that I can refresh the token and then continue with the rest of the call?
Edit:
I am importing from via import { Observable, from } from "rxjs";.
So, I thought the error was coming from the line .pipe(flatMap(resp =>... but I was wrong. The error is coming from the method which is calling this.
GetInitialLinkList(): Observable<Institution[]>
{
let base = { 'MemberId': localStorage.getItem('memberId') };
let ins = localStorage.getItem("initialInstitutionList");
if (ins)
{
return of(JSON.parse(ins));
}
return this._settingsService.get().pipe(
flatMap(settings =>
{
this._settings = settings;
return this._api.request<Institution[]>("Post", `${this._settings.mea}/GetInitialLinkList`, { body: base })
.pipe(
retry(1),
catchError(this.handleError)
)
.pipe(flatMap(instList =>
{
localStorage.setItem("initialInstitutionList", JSON.stringify(instList));
return of(instList);
}))
}));
}
and that is being subscribed to inside my component:
private GetLinkList()
{
this.showWaiting.emit(true);
this._data.GetInitialLinkList().subscribe((result) =>
{
this.initialList = result;
this.showWaiting.emit(false);
});
}
From what Brandon said (I forgot to return /facepalm...) I added the return so I have return from(this._store.refreshBearerToken()) which changed my error to
ERROR Error Code: undefined
Message: You provided an invalid object where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.
defaultErrorLogger # core.js:6014
Can you show the actual error and the line in the code that the error occurs on? Also show where and how you import from.
I notice your code snippet does not return the observable it builds up via from(...).pipe(...) nor does it subscribe to it. It might help to show how your code actually uses this observable.

How to get each http body updates on angular Http request?

I'm using an express api (my back-end) and an angular app (my front-end).
One express js end point (let's call it '/foo') is processing a lot of files,
i send data using res.write() after each treatment so the http response body is update.
I would like to get this update on my angular app.
I was using ajax in a previous version and it worked fine with ajax call :
xhrFields: {
// Getting on progress streaming response
onprogress: function(e)
{
var progressResponse;
var response = e.currentTarget.response;
if(lastResponseLength === false)
{
progressResponse = response;
lastResponseLength = response.length;
}
else
{
progressResponse = response.substring(lastResponseLength);
lastResponseLength = response.length;
}
actualResponse += progressResponse
}
Unfortunatly i found nothing to get partial http body. I tried to use 'reportProgress' Parameter but it's not working.
For some more context my front-end angular code:
service.ts :
setHolidaysDirectory(holidaysKey: string, path: string): Observable<Object>{
const setHolidayDirectoryStreamHttpRequest =
new HttpRequest('POST', 'http://localhost:8089/holidays/pictures/edit', { 'key': holidaysKey,
'path': path
}, {headers: this._httpHeaders, reportProgress: true, responseType: 'text'});
// pipe stream answer
return this._http.request(setHolidayDirectoryStreamHttpRequest);
}
and my component just call the service and subscribe :
this._holidaysService
.setHolidaysDirectory(key, finalHolidaysForm.path)
.subscribe((stream) => {
console.log('new answer');
console.log(stream);
}, error => console.log(error));
But unfortunatly i got empty answer and all the http body is recovered after res.end() (server side)
Can anyone help pls !
Thank a lot !

Ajax request with CORS redirect fails in IE11

I'm trying to make an ajax request to a resource on the same domain. Under certain circumstances the request gets redirected(303) to an external resource. The external resource supports CORS.
In browsers like Chrome, Firefox or Safari the request succeeds.
In IE11 the request fails with error:
SCRIPT 7002: XMLHttpRequest: Network Error 0x4c7, The operation was canceled by the user
The ajax request is made with jQuery:
$.ajax({
url: "/data",
type: "POST",
dataType: "json",
contentType: "application/json;charset=UTF-8",
data: JSON.stringify({name: 'John Doe'})
}).done(function () {
console.log('succeeded');
}).fail(function () {
console.log('failed');
});
I've build a little example which demonstrates the problem. You could see the code here.
w/o redirect
w/ redirect
Is there a way to solve this problem? What am I missing?
In the initial definition of the CORS-standard, redirects after a successful CORS-preflight request were not allowed.
IE11 implements this (now outdated) standard.
Since August 2016, this has changed, and all major browsers now support it (Here's the actual pull request).
I'm afraid to support <=IE11 you'll have to modify your server-side code as well to not issue a redirect (at least for <=IE11).
Part 1) Server-side (I'm using node.js express here):
function _isIE (request) {
let userAgent = request.headers['user-agent']
return userAgent.indexOf("MSIE ") > 0 || userAgent.indexOf("Trident/") > 0
}
router.post('data', function (request, response) {
if (_isIE(request)) {
// perform action
res.set('Content-Type', 'text/plain')
return res.status(200).send(`${redirectionTarget}`)
} else {
// perform action
response.redirect(redirectionTarget)
}
})
Part 2 Client-side
Note: This is pure Javascript, but you can easily adapt it to your jQuery/ajax implementation.
var isInternetExplorer = (function () {
var ua = window.navigator.userAgent
return ua.indexOf("MSIE ") > 0 || ua.indexOf("Trident/") > 0
})()
function requestResource (link, successFn, forcedRedirect) {
var http
if (window.XMLHttpRequest) {
http = new XMLHttpRequest()
} else if (window.XDomainRequest) {
http = new XDomainRequest()
} else {
http = new ActiveXObject("Microsoft.XMLHTTP")
}
http.onreadystatechange = function () {
var OK = 200
if (http.readyState === XMLHttpRequest.DONE) {
if (http.status === OK && successFn) {
if (isInternetExplorer && !forcedRedirect) {
return requestResource(http.responseText, successFn, true)
} else {
successFn(http.responseText)
}
}
}
}
http.onerror = http.ontimeout = function () {
console.error('An error occured requesting '+link+' (code: '+http.status+'): '+http.responseText)
}
http.open('GET', link)
http.send(null)
}
its already answered - have a look - https://blogs.msdn.microsoft.com/webdev/2013/10/28/sending-a-cors-request-in-ie/

Ember Simple Auth on Firefox: authentication throws Error

I am extending Ember Simple Auth's base authentication class to allow authentication with Google. So far, it works on Safari 8 and Chrome 41 (both on Yosemite) with no errors. However, on Firefox 35, it throws an Error that does not occur on the other browsers. Here is my Google authenticator class:
App.GoogleAuthenticator = SimpleAuth.Authenticators.Base.extend({
// constants for Google API
GAPI_CLIENT_ID: 'the client id',
GAPI_SCOPE: ['email'],
GAPI_TOKEN_VERIFICATION_ENDPOINT: 'https://www.googleapis.com/oauth2/v2/tokeninfo',
// method for scheduleing a single token refresh
// time in milliseconds
scheduleSingleTokenRefresh: function(time) {
var self = this;
return new Ember.RSVP.Promise(function(resolve, reject) {
Ember.run.later(self, function() {
gapi.auth.authorize({
client_id: self.GAPI_CLIENT_ID,
scope: self.GAPI_SCOPE,
immediate: true
}, function(data) {
if (data && !data.error) {
resolve(data);
} else {
reject((data || {}).error);
}
});
}, time);
});
},
// WIP: recursive method that reschedules another token refresh after the previous scheduled one was fulfilled
// usage: scheduleTokenRefreshes(time until token should refresh for the first time, time between subsequent refreshes)
// usage: scheduleTokenRefreshes(time between refreshes)
scheduleTokenRefreshes: function(time1, time2) {
var self = this;
// if there is a time2, schedule a single refresh, wait for it to be fulfilled, then call myself to schedule again
if (!Ember.isEmpty(time2)) {
self.scheduleSingleTokenRefresh(time1)
.then(function() {
self.scheduleTokenRefreshes(time2);
});
// if there isn't a time2, simply schedule a single refresh, then call myself to schedule again
} else {
self.scheduleSingleTokenRefresh(time1)
.then(function() {
self.scheduleTokenRefreshes(time1);
});
}
},
// method that restores the session on reload
restore: function(data) {
var self = this;
return new Ember.RSVP.Promise(function(resolve, reject) {
console.log(data);
if (Ember.isEmpty(data.access_token)) {
reject();
return;
}
// schedule a refresh 15 minutes before it expires or immediately if it expires in < 15
var timeNow = Math.floor(Date.now() / 1000);
var expiresAt = +data.expires_at;
var timeDifference = expiresAt - timeNow;
var schedulingDelay = Math.floor(timeDifference - 15 * 60);
schedulingDelay = schedulingDelay < 0 ? 0 : schedulingDelay;
self.scheduleTokenRefreshes(schedulingDelay * 1000, 45 * 60);
resolve(data);
});
},
// method that authenticates
authenticate: function() {
var self = this;
return new Ember.RSVP.Promise(function(resolve, reject) {
gapi.auth.authorize({
client_id: self.GAPI_CLIENT_ID,
scope: self.GAPI_SCOPE
}, function(data) {
if (data && !data.error) {
// schedule a refresh in 45 minutes
var schedulingDelay = 45 * 60;
self.scheduleTokenRefreshes(schedulingDelay * 1000);
resolve(data);
} else {
reject((data || {}).error);
}
});
});
},
// method that logs the user out and revokes the token
invalidate: function(data) {
var self = this;
return new Ember.RSVP.Promise(function(resolve, reject) {
// send a GET request to revoke the token
Ember.$.ajax({
type: 'GET',
url: 'https://accounts.google.com/o/oauth2/revoke?token=' + self.get('session.access_token'),
contentType: 'application/json',
dataType: 'jsonp'
})
.done(function(successData) {
resolve(successData);
})
.fail(function(error) {
reject(error);
});
});
}
});
When the popup window closes after a successful login on Google's end, this error appears on Firefox's console:
Error: Assertion Failed: Error: Permission denied to access property 'toJSON' ember.js:13749
"__exports__.default<.persist#http://127.0.0.1/~jonchan/test/bower_components/ember-simple-auth/simple-auth.js:1524:1
__exports__.default<.updateStore#http://127.0.0.1/~jonchan/test/bower_components/ember-simple-auth/simple-auth.js:1195:11
__exports__.default<.setup#http://127.0.0.1/~jonchan/test/bower_components/ember-simple-auth/simple-auth.js:1149:9
__exports__.default<.authenticate/</<#http://127.0.0.1/~jonchan/test/bower_components/ember-simple-auth/simple-auth.js:1066:13
tryCatch#http://127.0.0.1/~jonchan/test/bower_components/ember/ember.js:47982:16
invokeCallback#http://127.0.0.1/~jonchan/test/bower_components/ember/ember.js:47994:17
publish#http://127.0.0.1/~jonchan/test/bower_components/ember/ember.js:47965:11
#http://127.0.0.1/~jonchan/test/bower_components/ember/ember.js:29462:9
Queue.prototype.invoke#http://127.0.0.1/~jonchan/test/bower_components/ember/ember.js:848:11
Queue.prototype.flush#http://127.0.0.1/~jonchan/test/bower_components/ember/ember.js:913:13
DeferredActionQueues.prototype.flush#http://127.0.0.1/~jonchan/test/bower_components/ember/ember.js:718:13
Backburner.prototype.end#http://127.0.0.1/~jonchan/test/bower_components/ember/ember.js:143:11
createAutorun/backburner._autorun<#http://127.0.0.1/~jonchan/test/bower_components/ember/ember.js:546:9
" ember.js:29488
Here is the version information:
DEBUG: Ember : 1.9.1
DEBUG: Ember Data : 1.0.0-beta.14.1
DEBUG: Handlebars : 2.0.0
DEBUG: jQuery : 2.1.3
DEBUG: Ember Simple Auth : 0.7.2
The most confounding thing is that this only appears on Firefox. Is it a bug in Ember Simple Auth or Ember? How do I fix it?
I do not know about only Firefox throwing an error (I've had a similar error with Chrome 40), but there is a bug in ember-simple-auth 0.7.2 with Ember 1.9 that prohibits sending an actual error response in the authenticate method in the authenticator.
If you return reject() in the rejection function of authenticate it will not throw an additional error. This will however not propagate the errorstatus or message, so I consider this a bug.
A work-around was proposed on github about this issue by setting Ember.onerror=Ember.K temporarily so additional errors will not be propagated, although it will propagate the original authenticate rejection with the error-status.
The issue in the github repo only mentions problems with testing this, but I've had this problem in normal code.
see: https://github.com/simplabs/ember-simple-auth/issues/407
Turns out the error was on the resolve part of the authenticate method. Here is what fixed it:
App.GoogleAuthenticator = SimpleAuth.Authenticators.Base.extend({
authenticate: function() {
return new Ember.RSVP.Promise(function(resolve, reject) {
gapi.auth.authorize({
client_id: 'the client id',
scope: ['the scopes'],
}, function(data) {
if (data && !data.error) {
resolve({
access_token: data.access_token // !! passing the entire 'data' object caused the error somehow
});
} else {
reject((data || {}).error);
}
});
});
},
// ...
});
I'm still not quite sure why this caused the error. Perhaps the Google API's response (in its entirety) is somehow incompatible with Ember Simple Auth.

Resources