Changes to how the forge request component works (in v2) - trigger.io

We've been running Forge v2 for just over a month now. We switched over on a delayed basis because our forge web app (on node) is heavily customized and migrating is a manual process.
A week ago I noticed that non 200OK HTTP statuses from our API were not being handled properly by our app. The problem seemed to be that the responseText property of the error object was missing. This is a pretty important property as we return error information as JSON when the request is non 200OK.
The v2 documentation refers to a content property and not responseText but both properties are missing - https://trigger.io/modules/request/current/docs/index.html
I can confirm that this functionality worked before because when I reverted the all.js file to a date before the v2 upgrade, it started working again. After some rooting about with the source, the problem seems to be with this method in the all.js file.
e.ajax({
url: "/_forge/proxy/" + p.match(/\/\/([^\/:]+)/)[1].split("").reverse().join("").replace(/\./g, "/") + "/",
type: "POST",
data: t,
dataType: "text",
timeout: w,
success: A &&
function(E) {
try {
if (x == "xml") {
var D, C;
if (window.DOMParser) {
D = new DOMParser();
C = D.parseFromString(E, "text/xml")
} else {
C = new ActiveXObject("Microsoft.XMLDOM");
C.async = "false";
C.loadXML(E)
}
E = C
} else {
if (x == "json") {
E = JSON.parse(E)
}
}
} catch(F) {}
A(E)
},
error: function(E, C, D) { //E variable has the responseText property, but is not passed along in the callback function
v({
message: "api.ajax with " + JSON.stringify(B) + "failed. " + C + ": " + D,
type: "EXPECTED_FAILURE",
status: C,
statusCode: E.status,
err: D
})
}
})
After unminifying the all.js file, it's around the 6800 line mark where you declare the request.ajax object.
Steps to reproduce
Make API call using forge.request with an error callback
Have a PHP file return JSON with a non 200OK status
Inside error callback, there is no responseText property to access the API response text
My Trigger control panel doesn't show that I need an update, and all modules are up to date as far as I know.
Is this a regression in forge functionality or is this intended functionality?

Related

Use MSAL in Microsoft Teams not work JS

I work on an MS Teams app with a connection to MS Graph.
I'm trying to get an access token for MS Graph in MS Teams. To get a token I'm using MSAL js.
If I run the App with gulp serve I receive a valid token and I have access to the MS Graph endpoints. But if I build the app and install it in MS Teams the function userAgentApplication.acquireTokenSilent(config) is never executed. I tested it with a console.log before and after the call. There is no error thrown.
Do you have any idea why the above snippet is not executed in MS Teams (app and webapp)?
NEW:
On Home:
export function login() {
const url = window.location.origin + '/login.html';
microsoftTeams.authentication.authenticate({
url: url,
width: 600,
height: 535,
successCallback: function(result: string) {
console.log('Login succeeded: ' + result);
let data = localStorage.getItem(result) || '';
localStorage.removeItem(result);
let tokenResult = JSON.parse(data);
storeToken(tokenResult.accessToken)
},
failureCallback: function(reason) {
console.log('Login failed: ' + reason);
}
});
}
On Login
microsoftTeams.initialize();
// Get the tab context, and use the information to navigate to Azure AD login page
microsoftTeams.getContext(function (context) {
// Generate random state string and store it, so we can verify it in the callback
let state = _guid();
localStorage.setItem("simple.state", state);
localStorage.removeItem("simple.error");
// See https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-v2-protocols-implicit
// for documentation on these query parameters
let queryParams = {
client_id: "XXX",
response_type: "id_token token",
response_mode: "fragment",
scope: "https://graph.microsoft.com/User.Read openid",
redirect_uri: window.location.origin + "/login-end.html",
nonce: _guid(),
state: state,
login_hint: context.loginHint,
};
// Go to the AzureAD authorization endpoint (tenant-specific endpoint, not "common")
// For guest users, we want an access token for the tenant we are currently in, not the home tenant of the guest.
let authorizeEndpoint = `https://login.microsoftonline.com/${context.tid}/oauth2/v2.0/authorize?` + toQueryString(queryParams);
window.location.assign(authorizeEndpoint);
});
// Build query string from map of query parameter
function toQueryString(queryParams) {
let encodedQueryParams = [];
for (let key in queryParams) {
encodedQueryParams.push(key + "=" + encodeURIComponent(queryParams[key]));
}
return encodedQueryParams.join("&");
}
// Converts decimal to hex equivalent
// (From ADAL.js: https://github.com/AzureAD/azure-activedirectory-library-for-js/blob/dev/lib/adal.js)
function _decimalToHex(number) {
var hex = number.toString(16);
while (hex.length < 2) {
hex = '0' + hex;
}
return hex;
}
// Generates RFC4122 version 4 guid (128 bits)
// (From ADAL.js: https://github.com/AzureAD/azure-activedirectory-library-for-js/blob/dev/lib/adal.js)
function _guid() {...}
on login end
microsoftTeams.initialize();
localStorage.removeItem("simple.error");
let hashParams = getHashParameters();
if (hashParams["error"]) {
// Authentication/authorization failed
localStorage.setItem("simple.error", JSON.stringify(hashParams));
microsoftTeams.authentication.notifyFailure(hashParams["error"]);
} else if (hashParams["access_token"]) {
// Get the stored state parameter and compare with incoming state
let expectedState = localStorage.getItem("simple.state");
if (expectedState !== hashParams["state"]) {
// State does not match, report error
localStorage.setItem("simple.error", JSON.stringify(hashParams));
microsoftTeams.authentication.notifyFailure("StateDoesNotMatch");
} else {
// Success -- return token information to the parent page.
// Use localStorage to avoid passing the token via notifySuccess; instead we send the item key.
let key = "simple.result";
localStorage.setItem(key, JSON.stringify({
idToken: hashParams["id_token"],
accessToken: hashParams["access_token"],
tokenType: hashParams["token_type"],
expiresIn: hashParams["expires_in"]
}));
microsoftTeams.authentication.notifySuccess(key);
}
} else {
// Unexpected condition: hash does not contain error or access_token parameter
localStorage.setItem("simple.error", JSON.stringify(hashParams));
microsoftTeams.authentication.notifyFailure("UnexpectedFailure");
}
// Parse hash parameters into key-value pairs
function getHashParameters() {
let hashParams = {};
location.hash.substr(1).split("&").forEach(function (item) {
let s = item.split("="),
k = s[0],
v = s[1] && decodeURIComponent(s[1]);
hashParams[k] = v;
});
return hashParams;
}
Even though my answer is quite late, but I faced the same problem recently.
The solution is farely simple: MSALs silent login does not work in MSTeams (yet) as MSTeams relies on an IFrame Approach that is not supported by MSAL.
You can read all about it in this Github Issue
Fortunately, they are about to release a fix for this in Version msal 1.2.0 and there is already an npm-installable beta which should make this work:
npm install msal#1.2.0-beta.2
Update: I tried this myself - and the beta does not work as well. This was confirmed by Microsoft in my own Github Issue.
So I guess at the time being, you simply can't use MSAL for MS Teams.

Parse server return invalid session token for public records and only when calling functions

ok this is something that start to show a few weeks ago, I have a parse server running on an ubuntu machine with version 2.3.3, I have a bunch of cloud functions running, nothing fancy just querying some specific Class, all the data in that class is public and all work most of the time just fine. However from time to time, calling a cloud function start to return invalid sessions error 209, even with a user logged or not, super weird, but what is even strange is that when that happens no one else can run the function, and every user start to got the same error.
The only way I can make it work again is restarting the server, also only happens with cloud functions and from the ios app, I saying this cause we have some other part calling functions from php but it seems those are not starting the problem
2017-02-21T01:26:57.676Z - Failed running cloud function partnersv2 for user undefined with:
Input: {"k":"","searchType":"","category":"comida"}
Error: {"code":141,"message":{"code":209,"message":"invalid session token"}}
2017-02-21T01:26:57.669Z - invalid session token
2017-02-21T01:26:55.738Z - ParseError { code: 209, message: 'invalid session token' }
2017-02-21T01:26:55.737Z - Error generating response. ParseError {
code: 141,
message: ParseError { code: 209, message: 'invalid session token' } }
I have no idea why is this happening, also I don't think is related to the legacy session cause this server and the user are new, we start developing this a few months ago is not a ported app from the old service
One thing we are doing a lot is removing from the dashboard sessions at will, cause we are testing and developing, not sure if this could be a reason, but what about when the user is undefined, it shouldn't even try to use session I think, or maybe a user that was actually logged could be the culprit, setting the server to VERBOSE is not telling anything else as well, just the params and the call which it doesn't look weird to me, am looking for someone that maybe can put me in the right direction of maybe how the session work or something, thank you for any help
EDIT 1:
This is the cloud function that is trowing the error
Parse.Cloud.define('partnersv2', function (req, res) {
var searchType = req.params.searchType,
k = req.params.k,
category = req.params.category,
query;
query = new Parse.Query('Partner');
query.addDescending('is_open');
query.equalTo('enabled', true);
query.equalTo('category', category);
query.select(['name', 'phone', 'delivery_eta', 'keys', 'price_range', 'is_new', 'cover', 'recommended', 'open_time', 'min_order', 'delivery_rank', 'logo', 'comingsoon', 'category', 'is_open']);
if (searchType !== '' && searchType !== undefined && k !== '' && k !== undefined) {
if (searchType === 'Tag') {
query.equalTo('tags', k);
} else {
query.equalTo('name', k);
}
}
query.limit(1000);
query.find({
success: function (results) {
if (results.length > 0) {
res.success(results);
} else {
res.error('404 not found');
}
},
error: function (error) {
res.error(error);
}
});
});
and this is a screenshot of the ACL col
Use Master Key
Parse.Cloud.define('partnersv2', function (req, res) {
//var token = req.user.getSessionToken() // getSession Token return r: (yourkey);
var searchType = req.params.searchType,
k = req.params.k,
category = req.params.category,
query;
query = new Parse.Query('Partner');
query.addDescending('is_open');
query.equalTo('enabled', true);
query.equalTo('category', category);
query.select(['name', 'phone', 'delivery_eta', 'keys', 'price_range', 'is_new', 'cover', 'recommended', 'open_time', 'min_order', 'delivery_rank', 'logo', 'comingsoon', 'category', 'is_open']);
if (searchType !== '' && searchType !== undefined && k !== '' && k !== undefined) {
if (searchType === 'Tag') {
query.equalTo('tags', k);
} else {
query.equalTo('name', k);
}
}
query.limit(1000);
query.find({
useMasterKey: true, // sessionToken: token
success: function (results) {
if (results.length > 0) {
res.success(results);
} else {
res.error('404 not found');
}
},
error: function (error) {
res.error(error);
}
});
});

React-Native Websocket Event data property is missing

I am trying to connect to the Watson TTS API over a Websocket connection in React-Native. The connection is established and I can send a message to the server, however the data that I get back from the server somehow always is empty.
It seems as if the event.data property is completely missing. If I log it to the console in react-native I get 'undefined' as a result. If i use the same code in the browser everything works perfectly.
I am using react-native 0.33 and here's my code:
function connectTTS(token) {
var voice = "de-DE_BirgitVoice";
var format = 'audio/basic';
var token = token;
var wsURI = "wss://stream.watsonplatform.net/text-to-speech/api/v1/synthesize?voice=" + voice + "&watson-token=" + token;
function onOpen(evt) {
var message = {
text: "Hello world.",
accept: format
};
// note: the Text to Speech service currently only accepts a single message per WebSocket connection
websocket.send(JSON.stringify(message));
}
var audioParts = [];
var finalAudio;
function onMessage(evt) {
console.log(evt.data);
if (typeof evt.data === 'string') {
console.log('Received string message: ', evt.data)
} else {
console.log('Received ' + evt.data.size + ' binary bytes', evt.data.type);
audioParts.push(evt.data);
}
}
function onClose(evt) {
console.log('WebSocket closed', evt.code, evt.reason);
console.log(audioParts);
console.log(format);
finalAudio = new Blob(audioParts, {type: format});
console.log('final audio: ', finalAudio);
}
function onError(evt) {
console.log('WebSocket error', evt);
}
var websocket = new WebSocket(wsURI);
websocket.onopen = onOpen;
websocket.onclose = onClose;
websocket.onmessage = onMessage;
websocket.onerror = onError;
}
It would be great if somebody with more react-native / websocket experience could help me find the solution. Thanks.
In react-native up to 0.53 (latest version at the moment), react-native WebSocket event processing relies on event-target-shim 1.1.1 lib which wraps an event and does not include data to the wrapped event, so in order to get WebSocket event data you may use one of two approaches:
Get data from __proto__;
Rewrite event-target-shim 1.1.1;
The first approach:
use <your event>.__proto__.__proto__.data
The second approach:
fork event-target-shim and reset to event-target-shim 1.1.1;
fork react-native;
Add the code listed below to the event-target-shim/lib/event-wrapper.js;
rewrite react-native package.json to use forked version of the event-target-shim;
rewrite package.json of your project;
Code to add in exports.createEventWrapper after var propertyDefinition = {...}:
if (event.type === "message"){
propertyDefinition.data = {value: event.data, enumerable: true};
}

ActionDispatch::ParamsParser::ParseError for String Request Payload

I'm receiving a standard request from an API. It looks something like this :
It's content type and length is :
But when this hits my Rails server, Rails responds with
The reason I'm bringing this up, is because the same request seems to work on SCORM Cloud's server. If I upload the exact same content to them, and watch it in the debugger, I see it send out an application/json statement with the same Request payload, but with no unexpected token error.
Does a Rails application/json request have to be written a certain way that differs from other servers? Is there a proper way to rewrite this line in Rack Middleware to prevent this error?
Update
The javascript :
function _TCDriver_XHR_request (lrs, url, method, data, callback, ignore404, extraHeaders) {
_TCDriver_Log("_TCDriver_XHR_request: " + url);
var xhr,
finished = false,
xDomainRequest = false,
ieXDomain = false,
ieModeRequest,
title,
ticks = ['/', '-', '\\', '|'],
location = window.location,
urlParts,
urlPort,
result,
extended,
until,
fullUrl = lrs.endpoint + url
;
urlParts = fullUrl.toLowerCase().match(/^(.+):\/\/([^:\/]*):?(\d+)?(\/.*)?$/);
// add extended LMS-specified values to the URL
if (lrs.extended !== undefined) {
extended = [];
for (var prop in lrs.extended) {
if(lrs.extended[prop] != null && lrs.extended[prop].length > 0){
extended.push(prop + "=" + encodeURIComponent(lrs.extended[prop]));
}
}
if (extended.length > 0) {
fullUrl += (fullUrl.indexOf("?") > -1 ? "&" : "?") + extended.join("&");
}
}
//Consolidate headers
var headers = {};
headers["Content-Type"] = "application/json";
headers["Authorization"] = lrs.auth;
if (extraHeaders !== null) {
for (var headerName in extraHeaders) {
headers[headerName] = extraHeaders[headerName];
}
}
//See if this really is a cross domain
xDomainRequest = (location.protocol.toLowerCase() !== urlParts[1] || location.hostname.toLowerCase() !== urlParts[2]);
if (! xDomainRequest) {
urlPort = (urlParts[3] === null ? ( urlParts[1] === 'http' ? '80' : '443') : urlParts[3]);
xDomainRequest = (urlPort === location.port);
}
//If it's not cross domain or we're not using IE, use the usual XmlHttpRequest
if (! xDomainRequest || typeof XDomainRequest === 'undefined') {
_TCDriver_Log("_TCDriver_XHR_request using XMLHttpRequest");
xhr = new XMLHttpRequest();
xhr.open(method, fullUrl, callback != null);
for (var headerName in headers) {
xhr.setRequestHeader(headerName, headers[headerName]);
}
}
//Otherwise, use IE's XDomainRequest object
else {
_TCDriver_Log("_TCDriver_XHR_request using XDomainRequest");
ieXDomain = true;
ieModeRequest = _TCDriver_GetIEModeRequest(method, fullUrl, headers, data);
xhr = new XDomainRequest ();
xhr.open(ieModeRequest.method, ieModeRequest.url);
}
Rails is being "helpful" here and assuming that the client is correctly using "Content-Type" and passing a value that actually matches that content type. In other words, the payload in the request has to be parseable JSON, and the value being passed is not valid JSON.
Which is an entirely reasonable thing for it to do when you are implementing an in house API that isn't intended for maximum interoperability. What Rails doesn't know is that an LRS' document storage is supposed to be "dumb" and basically allow the client to shove whatever it wants in and get whatever it wants out, which is why SCORM Cloud accepts the request, basically it just stores the content type and the contents, and then regurgitates them as is on request.
The code you pasted is from a very old library that has poor implementation of Content-Type headers. If this code is found anywhere other than in a relatively old version of a piece of content from one of the major e-learning authoring tools then it should be updated to use a recent version of TinCanJS and improve the content type handling.
As far as making this work on Rails, sorry I don't have that much experience with it. Presumably there is a switch or something to turn off automatic request body parsing, at least that's what most other frameworks I've used have.
Does a Rails application/json request have to be written a certain way that differs from other servers?
Not that I know of no.
Is there a proper way to rewrite this line in Rack Middleware to prevent this error?
There might a way yes, maybe even without rack middlewares, although it's quite hard to help you without an actual request to work with.

XMLHttpRequest different in IE8 vs. FireFox/Chrome

I'm having a problem similar to jQuery $.ajax Not Working in IE8 but it works on FireFox & Chrome, but with a different use case.
I'm using the jQuery Form plug-in to handle a file upload to an ASP.NET MVC controller, which sends the file off for parsing and processing. If an Exception is thrown, it should alert the user to the issue.
//client side code
//make an ajax call, sending the contents of the file
$("#ajaxUploadForm").ajaxSubmit({
dataType: 'json',
url: '/plan/Something/ExcelImport',
iframe: true,
beforeSend: function () {
$(".ui-dialog-buttonpane").find("#my_progress").fadeIn();
},
success: function (data, textStatus) {
output = "<center><span class='flash'>" + data.message + "</span></center>";
$("#flash_message").html(output).fadeIn('slow');
setTimeout(function () { $("#flash_message").fadeOut() }, 5000);
cleanup();
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
alert("XMLHttpRequest is " + XMLHttpRequest);
var contents = "";
for (prop in XMLHttpRequest) {
contents += "\na property is " + prop + " it's value is " + XMLHttpRequest[prop];
}
alert("the contents are " + contents);
alert("textStatus is " + textStatus);
alert("errorThrown is " + errorThrown);
//comes back in an HTML envelope. This should be parsed with regex, but I can't get it to work. Dirty hack
response = XMLHttpRequest.responseText.substring(XMLHttpRequest.responseText.indexOf("<body>"));
response = response.replace("<body>", "");
response = response.replace("</body>", "");
alert("There was a problem with the upload.\r\n" + response);
},
complete: function (XMLHttpRequest, textStatus) {
$(".ui-dialog-buttonpane").find("#my_progress").remove();
something_import.dialog('close');
something_import.dialog('destroy');
}
});
//server side code
public FileUploadJsonResult ExcelImport()
{
FileUploadJsonResult result = new FileUploadJsonResult();
HttpPostedFileBase hpf = Request.Files[0] as HttpPostedFileBase;
if (hpf.ContentLength == 0)
return new FileUploadJsonResult { Data = new { message = "File contained no data" } };
String fileName = Path.GetFileName(hpf.FileName);
String timeStampedFile = fileName.Insert(fileName.IndexOf('.'),"_"+DateTime.Now.ToFileTimeUtc());
string savedFileName = Path.Combine(AppDomain.CurrentDomain.BaseDirectory + "tempo", timeStampedFile);
hpf.SaveAs(savedFileName);
try
{
result = ProcessFile(savedFileName, Request["Id"]) as FileUploadJsonResult;
}
catch (ArgumentException e)
{
this.Response.StatusCode = 500;
this.Response.StatusDescription = System.Net.HttpStatusCode.BadRequest.ToString();
Response.Write(e.Message);
result = Json(new { message = e.Message, stackTrace = e.StackTrace }) as FileUploadJsonResult;
}
return result;
}
This works perfectly in Chrome and Firefox. In IE, the XMLHttpRequest object coming back is different:
FF:
IE:
I've been Googling around for differences between the browser implementations of XMLHttpRequest, but haven't found anything that deals specifically with this case. Stymied.
The reason this is happening is because of the iframe fallback strategy that ajaxSubmit employs. I think since the response gets posted into the iframe IE tries to figure out how to dipslay it and decides that it wants to ask you to download the response instead of just putting it in the iframe.
I came across this same situation a while ago and found an article (that I can't find now) that offered a workaround.
If you surround your json response in a textarea nobody is going to complain(IE,FF,Chrome,probably Safari) and you'll get your response parsed correctly.
E.g. if you are returning
{Id: 1, Name: 'Me'}
just return:
<textarea>{Id: 1, Name: 'Me'}</textarea>
You see now IE thinks it's html so it inserts it into the hidden iframe. Your ajaxSubmit function still gets called and parses the json correctly and then everybody's happy. :)
If you're using ASP.NET MVC you could shamelessly copy this extension method :)
public static class ControllerExtensions
{
public static ActionResult JsonSafe(this IController controller, object obj)
{
JavaScriptSerializer serializer = new JavaScriptSerializer();
return new WriteResult(string.Format("<textarea>{0}</textarea>", serializer.Serialize(obj)));
}
}
The wikipedia article on XMLHttpRequest seems to give a good overview of the history behind the XMLHttpRequest. It seems Microsoft and Mozilla developed/adopted their own versions of the object and hence why you are probably seeing different properties.
Here is a link to Microsoft's implementation of the XMLHttpRequest interface members, which seem to match the properties in your alert.
Here is the a link to Mozilla's implementation of XMLHttpRequest.
So while we wait for the W3C to standardize the XMLHttpRequest you will continue to have different implementations across the browsers like you are seeing in this case.
For some added fun here is Apple's and Opera's specifications on XMLHttpRequest.

Resources