Outlook Add-in - AttachmentId is malformed - outlook

We've been having issues intermittently where we get an error when downloading email item content from EWS "AttachmentId is malformed". This is for ItemAttachment (Especially .eml files)
We could not figure why or how this is happening and noticed that the ones that were failing had + and / in the id's. Searching across the web landed me on this article. Although this article is from 2015, wondering if this is still happening.
This article blew my mind and made sense (kind of) and implementing the conversion of + -> _ and / -> - worked fine, for a while.
We are now receiving the same error 'AttachmentId is malformed' and again could not find why, I removed the custom sanitizer function that replaces these characters and it started working again.
I have no idea what and why is this happening and how to reliably get attachment content. Currently, I've moved the sanitizer into the catch handler, so if for some reason the AttachmentId fails, we'll retry it by sanitizing it. Will have to keep an eye on how many fail.
Any light on this issue will be really appreciated.
Update 1.0 - Sample Code
Front-end
//At this point we've got the email and got the files
//We call EWS only if file.type == Office.MailboxEnums.AttachmentType.Item
//For all other files we call REST endpoint ~ Office.context.mailbox.restUrl + '/v2.0/'......
//Sample code below if only for EWS call
let files = this.email.attachments || [];
files.map(file => {
this._getEmailContent(file)
.then(res => {
return res;
});
})
//Get content from EWS
_getEmailContent(file, _failed){
//attachmentId
//Most of the times this will be fine, but at times when Id has a `+` or `/` if fails, Was expecting the convertToEwsId to handle any sanitization required.
let attachmentId = Office.context.mailbox.convertToEwsId(file.id, Office.MailboxEnums.RestVersion.v2_0);
return this.getToken(EWS)
.then(token => {
return this.http.post(`${endpoint}/downloadAttachment`,{
token: token,
url: Office.context.mailbox.ewsUrl,
id: attachmentId
},{
responseType: 'arraybuffer',
}).then(res => res.data);
}).catch(err => {
attachmentId = attachmentId.replace(/\+/g, "_");
this._getEmailContent(attachmentId, true);
})
}
Back-end
[HttpPost]
public DownloadAttachment(Request model){
var data = service.DownloadAttachment(model);
if(data == null)
{
return BadRequest("Error downloading content...");
}
return new HttpResponseMessage(HttpStatusCode.OK)
{
return data;
}
}
//Inside service
public byte[] DownloadAttachment(Request request){
var ser = new ExchangeService
{
Credentials: request.token,
Url = request.url
}
//Here it fails intermittently, returning AttachmentId is malformed.
var attachment = ser.GetAttachments(new [] {request.attachmentId}, null, null).First();
if (attachment is FileAttachment)
{
FileAttachment fileAttachment = attachment as FileAttachment;
fileAttachment.Load();
return fileAttachment.Content;
}
}

Related

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.

Cannot get the getCallbackTokenAsync to function correctly

I am a newbie to outlook js. I am developing a very simple add-in. The add-in simply will forward a selected email to a defined email address. So we click a button and forward the message. My command handler gets called, but that is about all I have gotten to work. The first problem is the authorization does not appear to work. I have followed the example on https://learn.microsoft.com/en-us/outlook/add-ins/use-rest-api.
The permission in my manifest is set to ReadWriteMailbox.
var accessToken;
Office.onReady(info => {
// If needed, Office.js is ready to be called
Office.context.mailbox.getCallbackTokenAsync({ isRest: true }, function(result) {
if (result.status === "succeeded") {
accessToken = result.value;
} else {
accessToken = "error";
}
});
});
function MyButtonClick(event) {
const message = {
type: Office.MailboxEnums.ItemNotificationMessageType.InformationalMessage,
message: "Performed action. Access token: " + accessToken,
icon: "Icon.80x80",
persistent: true
}
Office.context.mailbox.item.notificationMessages.replaceAsync("action", message);
event.completed();
}
I have tried moving the getCallbackTokenAsync all around, but it seems not to work properly. The accessToken is always undefined.
I have been messing with this for the past day. So I am assuming I am missing something.
We are primarily targeting Outlook 2016 on the mac and windows 10.
Any thoughts?
Tom

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 !

How to update an User with useMasterKey in Parse

Issue Description
I'm trying to update an User when another user click on my Xamarin button.
Then, I used Cloud Code to perform this but it doesnt work
My Code
Here is my complete JS code :
Parse.Cloud.beforeSave("Archive", function(request, response) {
Parse.serverURL = 'https://pg-app-0brffxkawi8lqvf2eyc2isqrs66zsu.scalabl.cloud/1/';
var status = request.object.get("status");
if (status == "validated") {
var event = request.object.get("event");
event.fetch({
success: function(myEvent) {
var coinsEvent = myEvent.get("coins");
var user = request.object.get("user");
user.fetch({
success: function(myUser, coinsEvent, user) {
var email = myUser.get("email");
var coinsUser = myUser.get("coins");
myUser.set("coins", coinsUser + coinsEvent);
return myUser.save(null, {useMasterKey:true});
}
});
}
});
}
response.success();
});
I think myUser.save(null, {useMasterKey:true}); should work
I actually have that error :
Dec 24, 2017, 12:27 GMT+1 - ERRORError generating response for [PUT] /1/classes/_User/1GPcqmn6Hd
"Cannot modify user 1GPcqmn6Hd."
{
"coins": 250
}
Environment Setup
Server
parse-server version : v2.3.3
Server: Sashido
Your success branch never calls response.success() which is a problem... though maybe not THE problem.
You are also doing 2 fetches inside a 'beforeSave' function which is not recommended. 'BeforeSave' must happen very quickly and fetches take time. I would consider thinking through other options.
If you really need to do it this way, consider doing a Parse.Query("event") with an include("user") and trigger the query with query.first({useMasterKey:true}).
Are you sure coinsEvent is what you think it is? Fetch only returns the object fetched... not sure that you can curry in other parameters. I would change your final success routine to (double checking that coinsEvent is valid):
success: function(myUser) {
var coinsUser = myUser.get("coins");
myUser.set("coins", coinsUser + coinsEvent);
return myUser.save(null, {useMasterKey:true}).then(_ => response.success());
}

Cannot submit form with supertest and mocha

I am using supertest and mocha to test a nodejs application. One of the things users can do is to submit a very simple form, which is picked up by the node server and parsed using formidable.
Here is the mocha test code:
var should = require('should'),
express = require('express'),
app = require('../app.js'),
request = require('supertest'),
csrfToken,
sessionId,
cookies = [];
describe('Post Handler', function(){
it('Uploads new post', function(done){
var req = request(app).post('/post?_csrf=' + csrfToken);
req.cookies = cookies;
req
.type('form')
.send({fieldTitle: 'autopost'})
.send({fieldContent: 'autocontent'})
.send({contentType: 'image/png'})
.send({blobId: 'icon_23943.png'})
.expect(200)
.end(function(error, res){
console.log('here');
done();
});
});
csrfToken retrieves a csrf token from the server, since I am using the csurf module and every POST method requires a csrf token. cookies stores the session cookie that is provided by the node server so I can persist the session between requests.
The form is processed by the following code:
//Takes HTTP form posted by client and creates a new post in the Db
exports.postPostUpload = function (req, res) {
var form = new formidable.IncomingForm();
form.parse(req, function (err, fields, files) {
console.log(err);
if (err) res.redirect(303, '/error');
else {
var new_post = new post_model.Post().createNewPost(fields);
new_post.setUserId(req.session.passport.user.userId);
new_post.uploadPostToDb(function (error, result) {
if (error) return res.status(500).end();
else {
if (new_post.media.contentType.indexOf('video') !== -1) {
addMessageToEncodingQueue(new_post, function (error, result, response) {
if (error) {
errorHelper.reportError({
stack: new Error().stack,
error: error
});
res.status(500).end();
}
else res.status(200).send(new_post.cuid);
});
}
else return res.status(200).send(new_post.cuid);
}
});
}
});
}
My current problem is, that once the form handler executes the line form.parse(req, function (err, fields, files) {, nothing happens. Formidable does not return error, it just does not return anything. Consequently, the mocha test never receives a reply from the server, and eventually the socket hangs and the test crashes. Needless to say, the form is successfully submit if you do it manually via the website.
There must be an error in the way supertest/mocha are executing this test, but I have not been able to find it. Any pointers are highly appreciated.

Resources