Outlook JS Add-in: EWS Calls using Ajax and Callback Token - javascript

The Office JS API has a 1 Mb limit on callbacks, and one of the requirements I have is to forward an existing email, designated as spam by the user, as an attachment to a 3rd party. I have extensive code to handle mail operations, using makeEwsRequestAsync, and for this case SOAP that works fine to return the MIME content to create and send the email, but not when the callback return is larger than 1 Mb.
To get around the 1 Mb limitation, it was suggested to others to make the EWS call directly, using a callback token, an item Id, and the EWS URL. I can easily get these components, and I have SOAP that works for the usual makeEwsRequestAsync method.
What I don't have is how to make the call to EWS call with the elements I have.
As an example, I used the following to get the EWS URL and item Id:
_mailbox = Office.context.mailbox;
_ewsUrl = _mailbox.ewsUrl;
var item = _mailbox.item;
_itemId = item.itemId;
I use this to get the callback token:
mailViaEws.Authenticate = function Start() {
getCallbackToken(setCallbackToken);
};
function getCallbackToken(callback) {
var options = {
isRest: false
};
Office.context.mailbox.getCallbackTokenAsync(options, callback);
}
function setCallbackToken(asyncResult) {
if (asyncResult.status === "succeeded") {
_callbackToken = asyncResult.value;
} else {
app.showNotification(_appName, asyncResult.error.message);
}
}
The SOAP is as follows, with a helper method to wrap the SOAP:
var soapToCreateItem =
'<soap:Body>' +
' <m:CreateItem MessageDisposition="SendAndSaveCopy"' +
' xmlns="http://schemas.microsoft.com/exchange/services/2006/messages" ' +
' xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types">' +
' <m:Items>' +
' <t:Message>' +
' <t:Subject>' + _subject + '</t:Subject>' +
' <t:Body BodyType="Text">' + _bodyText + '</t:Body>' +
' <t:ToRecipients>' + _adddressesSoap + '</t:ToRecipients>' +
' </t:Message>' +
' </m:Items>' +
' </m:CreateItem>' +
' </soap:Body>';
var soapToCreateItemData = getSoapBoadyEnvelope(soapToCreateItem);
I've search extensively, and although I've found code that use an intermediate service to call the EWS Managed API in C#, I am trying to make the call directly to EWS. Any help would be greatly appreciated.

Your Mailbox (_mailbox = Office.context.mailbox;) object has a method makeEwsRequestAsync that you can use to make SOAP call to the EWS.
Note, that you don't need to provide access token explicitly.
Here is more information.

The Callback token you get from getCallbackTokenAsync is only good for 5 minutes and is only good for the GetAttachment and GetItem EWS operations this is documented in https://learn.microsoft.com/en-us/office/dev/add-ins/reference/objectmodel/requirement-set-1.5/office.context.mailbox so your CreateItem request won't work in this context. Does the message need to come from the user if it just SPAM reporting? it would be easy just to do the send part using a Service Account with EWS or even just send it via SMTP.

Related

Create SharePoint document location using Client API (JavaScript) in Powerapps

In a model-driven powerapp I’m trying to create SharePoint document location using JavaScript and Client API that should be related to a custom entity.
Here is my code:
**//creates records in table via WebAPI call
function createRecordForTable(tableName, dataObj) {
// create table record
Xrm.WebApi.createRecord(tableName, dataObj).then(
function success(result) {
console.log("Record created with ID: " + result.id);
},
function (error) {
console.log(error.message);
}
);
}
//myresponse is object that holds some of needed values
//hel__exceptions is custom entity
const data = new Object();
data.name = myresponse.name;
data.relativeurl = myresponse.relativeurl;
data.absoluteurl = myresponse.absoluteurl;
data["parentsiteorlocation_sharepointdocumentlocation#odata.bind"] = "/sharepointdocumentlocations/(" + myresponse.sharepointdocumentlocationid + ")";
data["regardingobjectid_hel__exceptions#odata.bind"] = "/hel__exceptionses/(" + exceptionId +")";
data.sitecollectionid = myresponse.sitecollectionid;
createRecordForTable("sharepointdocumentlocation", data);**
The error received is: Empty segment encountered in request URL. Please make sure that a valid request URL is specified.
It looks like I’m missing some data that I need to pass with the call Xrm.WebApi.createRecord in order to create the record in sharepointdocumentlocation table.
Please help!

Firebase function - http get parameters with acentuation

I have a firebase function doing a http GET. There are 3 parameters and all works ok but if one of the parameters contains acentuation the Firebase console don't show any error but the the GET is not executed. In this case, the problem is with Parameter03.
var url = 'http://myapi.azurewebsites.net/api/values?Parameter01=' + nameParam + '&Parameter02=' + emailParam + '&Parameter03=' + serviceParam ;
http.get(url, (resp) => {
res.setEncoding('utf8');
}).on("error", (err) => {
console.log("Error : " + err.message);
});
Any help please ?
Whenever you build a URL, you should properly escape all the query string components so that they contain only valid characters. That's what encodeURIComponent() is for. So do encode all your query string values like this instead:
var url = 'http://myapi.azurewebsites.net/api/values' +
'?Parameter01=' + encodeURIComponent(nameParam) +
'&Parameter02=' + encodeURIComponent(emailParam) +
'&Parameter03=' + encodeURIComponent(serviceParam);
There are other cleaner ways to build a URL with query string components, but this should work fine.

UserCannotBeAlteredWithoutSessionError when updating PFUser on Parse from Heroku node.js app

I'm trying to update a PFUser in my Parse database from a node.js app running on Heroku. I'm calling the Parse cloud function from an iOS app.
Here's the part of the code I use to update the user on parse as well as creating the user on Stripe (the Stripe part works fine):
Parse.Cloud.define("createCustomerWithCreditCardToken", function(request, response) {
var userId = request.user.id;
var sourceId = request.params.creditCardToken;
var customerId;
var userSessionToken = request.user.getSessionToken();
console.log('userId: ' + userId + ' source: ' + sourceId + ' userSessionToken: ' + userSessionToken);
stripe.customers.create({
source: sourceId,
description: userId
}, function(error, customer) {
if (error !== null) {
response.error('error creating customer: ' + error);
}else {
var userQuery = new Parse.Query('User');
userQuery.equalTo('objectId', userId);
userQuery.first({sessionToken: userSessionToken}).then(function(user) {
console.log('user from parse query: ' + user.get("username"));
user.set("stripeCustomerId", customer.id);
user.save(null, {
success: function(parseCustomer) {
console.log('customer saved to parse: ' + parseCustomer);
},
error: function(error, parseCustomer) {
console.log('customer save failed: ' + JSON.stringify(error, null, 2) + ' with error: ' + JSON.stringify(parseCustomer,null, 2));
}
});
});
customerId = customer.id;
console.log('customerId: '+ customerId);
// response.success(customer);
response.success('Customer: ' + JSON.stringify(customer, null, 2) + 'error: ' + error);
}
});
});
I get the following error log output when I run this:
error log output
error: { "code": 206, "message": "Parse::UserCannotBeAlteredWithoutSessionError" }
In this post the current user concept in a node.js app context is discussed by a Parse engineer.
Also in Cloud Code, the concept of a method that returns the current
user makes sense, as it does in JavaScript on a web page, because
there’s only one active request and only one user. However in a
context like node.js, there can’t be a global current user, which
requires explicit passing of the session token.
Essentially he advises to do this:
Parse.Cloud.define('findBacon', function(req, res) {
var token = req.user.getSessionToken();
var query = new Parse.Query('Bacon');
// Pass the session token to the query
query.find({ sessionToken: token }).then( ... );
});
I have also tried using {useMasterKey:true} instead of {sessionToken:userSessionToken} which produces the same error.
I might just be missing some obvious detail, but I just can't seem to find it. Any ideas on how to solve this are greatly appreciated.
Turns out there's a third way of handling credentials:
Parse.Cloud.useMasterKey();
I placed this line in the beginning of the entire method, that did it for me. I'm not sure of the implications of giving the whole function these credentials though.
I'm not sure when you would use the other options either.
If someone comes across this and would like to elaborate, I'll be happy to give the right answer to a good explanation of when to grant which credentials.

pass multiple arguments __doPostBack

Is it possible to pass multiple arguments in the __doPostBack call?
I Know how I use one arrgument
__doPostBack('Service', $("input[name=celservice]:checked").val());
and in code behind vb.net
If eventTarget = "Service" Then
'Something
EndIf
but how when I use two arguments
We have created our own library to pass data from web page to ASP.NET. Without getting too much into details, the javascript code wraps info into an XML stream (you can use JSON just as well) that is then parsed on the server.
At its core, our javascript object looks something like this:
function MyArgs() {
var args = '';
this.add = function (id, value) {
args += ('<param id="' + encodeURI(id) + '" value="' + encodeURI(value) + '" />');
};
this.output = function () {
return '<request>' + args + '</request>';
};
}
And we use it like this:
var args = new MyArgs();
args.add("param1", "value A");
args.add("param2", "Value B");
__doPostBack("YOUR_SERVICE", args.output());
We like it because we are quite comfortable with XML data, but you may as well use JSON or URL encoding. The logic is the same: you create a stream that you can parse on the server.
This technique is also useful the other way around, server to client, especially as a response to AJAX requests.
I hope this may be of help to you :)
You can wrap your multiple results in an object, like so:
var result = {
celservice: $("input[name=celservice]:checked").val(),
somethingElse: true
}
then
__doPostBack('Service', result);

Twitter OAuth1.0A Javascript Error

I'm currently working on incorporating an authorization feature for Twitter following the approach described here: https://dev.twitter.com/docs/auth/implementing-sign-twitter. I'm using Ajax to send my POST 'http' request, but I've been constantly running into a '401: Unauthorized' error. My code is below:
function getTweets() {
var time = generateTimestamp();
var nonce = generateNonce();
var signature = generateSignature(time, nonce);
var headers = {
"Authorization": 'OAuth oauth_callback="http%3A%2F%2Fwww.google.com%2F", oauth_consumer_key="eEeAAz9fakedtAOlIUhPgQ", oauth_nonce="bbc34b2ca6faabogus6dfc025907fa334", oauth_signature="' + signature + '", oauth_signature_method="HMAC-SHA1", oauth_timestamp="' + time + '", oauth_version="1.0"'
};
$.ajax({
type: "POST",
url: "https://api.twitter.com/oauth/request_token",
dataType: "text",
headers: headers,
success: function(data) {
alert("Success!");
console.log(data);
},
error: function(jq) {
alert("Request Failed.");
console.log(jq.statusText);
}
});
}
function generateTimestamp() {
var currentTime = new Date;
currentTime = Math.floor(currentTime.getTime() / 1000);
return currentTime;
}
function generateNonce() {
var code = "";
for (var i = 0; i < 20; i++) {
code += Math.floor(Math.random() * 9).toString();
}
return code;
}
function generateSignature(timestamp, nonce) {
var http_method = "POST";
var base_url = "https://api.twitter.com/oauth/request_token";
var consumer_key = "eEeAAz9hUKtdjunkeIUhPgQ";
var consumer_secret = "c7wHxnjubxVDcc5hYFqnotactuallymysecretWs2XazUFde0lPRBtBQ";
var signature_method = "HMAC-SHA1";
var token = "609493744-kNPzLKSI4Hg9NWQnopeFPb91eXFUutFm1nZ2hDk2";
var token_secret = "15WOJS9Ji1AXsKRkyAZrxKdsalted5Gj5ZyEAb9aVrJxI";
var version = "1.0";
var parameter_string = "oauth_callback=" + encodeURIComponent(base_url) + "&oauth_consumer_key=" + consumer_key + "&oauth_nonce=" + nonce + "&oauth_consumer_key=" + consumer_key + "&oauth_signature_method=" + signature_method + "&oauth_timestamp=" + timestamp +"&oauth_version=" + version;
var base_string = http_method + "&" + encodeURIComponent(base_url) + "&" + encodeURIComponent(parameter_string);
var signing_key = encodeURIComponent(consumer_secret) + "&";
var signature = encodeURIComponent(window.btoa(CryptoJS.HmacSHA1(base_string, signing_key)));
alert(signature);
return signature;
}
Feel free to post below if there's any other information that would make this error clearer. Thanks.
I created a node.js library to mess around with the Twitter OAuth dance and API. Code is here, tweeter.js
You're welcome to walk through the logic for creating the header and signature (starting at line 348 )
One thing I don't see in the code you've posted and which will make a huge difference is that the signature string must be generated to include the original header, then the header must be rebuilt with the generated string. It's a huge pain and it took me a while to figure it out.
Although the code I wrote is geared toward node.js, you should be able to reuse a lot of the logic to meet your needs.
EDIT
I found a site called hueniverse documented OAuth very well. In fact, there is a utility here to build your own headers for validating your logic (select the 'Create your own' radio button).
EDIT 2
To better explain including the oauth_signature value in the header, suppose you have all of the data up to this point:
var headerObj = {
oauth_consumer_key="123456789",
oauth_token="11111",
oauth_nonce="asdfghjkl%3B",
oauth_timestamp="1341852000",
oauth_signature_method="HMAC-SHA1",
oauth_version="1.0"
};
You create the HMAC-SHA1 signature and receive: "jBpoONisOt5kFYOrQ5fHCSZBGkI%3D"
You would then add that return value to headerObj, giving you:
headerObj = {
oauth_consumer_key="123456789",
oauth_token="11111",
oauth_nonce="asdfghjkl%3B",
oauth_timestamp="1341852000",
oauth_signature_method="HMAC-SHA1",
oauth_version="1.0",
oauth_signature="jBpoONisOt5kFYOrQ5fHCSZBGkI%3D"
};
And this modified version of headerObj is what you build your HTTP headers from.
GET / HTTP/1.1
Host: api.twitter.com:443
Authorization: OAuth realm="https://api.twitter.com/",
oauth_consumer_key="123456789",
oauth_token="11111",
oauth_nonce="asdfghjkl%3B",
oauth_timestamp="1341852000",
oauth_signature_method="HMAC-SHA1",
oauth_version="1.0",
oauth_signature="jBpoONisOt5kFYOrQ5fHCSZBGkI%3D"
NOTE: I didn't verify the host/realm/port, so these are probably wrong. Check the API for those.
The reason this is done is that on Twitter's side (this is an OAuth implementation detail), the oauth_signature value is removed and the rest of the header is hashed and its return value is compared to the value sent in oauth_signature. It's sort of like a wax seal on an envelope... if the hash of the rest of the header doesn't match the hash value you sent in oauth_signature, Twitter knows not to trust the sender or the contents.
EDIT 2.5
I'm moving this from the comment to the answer.
If you check out this line in tweeter.js, you'll see the logic.
var signature = self.oauthSignature(method, path, headerObj, query);
headerObj.oauth_signature = qs.escape(signature);
// concat the header object into a csv string
var header = 'OAuth realm="Twitter API",';
var oauthParts = [];
for (var h in headerObj) {
oauthParts.push(h + '="'+headerObj[h]+'"');
}
header+= oauthParts.join(',');
//...
return header;
This bit of code does as I've explained in EDIT 2, by converting a JSON object into key="value" strings stored in oauthParts[], then joins each element in that array into a single comma-separated string which begins with OAuth realm="Twitter API",

Categories

Resources