How Content Security Policy Affects Office Add-ins

I now have several add-ins in AppSource and all doing well. But one in particular is getting a LOT more attention than others: “Send to Planner.” While popular with the general public, IT admins are not because it needs so many permissions. I wrote a fairly complex explanation to why it needs to many permissions: https://kryl.com/?page=kb&id=31. Ok, so some say – fine, I trust that, but others want more, a lot more, like my latest Penetration Testing Results. They also ask for more than attestation and wonder why I am not on the Office 365 certified list… me too, it turns out…

Sidebar first… This is leading somewhere, I promise… So, Office 365 Certification… I jump through God knows how many hoops to try to get my Office 365 certification rather than just a generic Publisher Attestation. I have to provide a TON of documentation and one of the artifacts is a good penetration test which are actually a LOT of work. Anyway, after a few weeks of gathering information, study on how to do this, that or the other thing, a ton of back-and-forth in email… I was told…

“Well, so, yeah… Your company is… yeah… just not big enough for us to consider you.”

So, when I submitted the VERY FIRST FORM with employee count you still let me proceed! I waste weeks, collecting and finagling and answering to artifacts and this… Ugh! Anyway, it was an educational experience and from that, I learned how to create a good Pen Test document.

…Back to the main attraction. Turns out to make administrators HAPPY, you need to have a really good Pen Test and one of the picker areas they remark upon is your sites Content Security Policy. I use an NGINX server in Azure. It took a LOT of finagling to get it restrictive enough for me to score an A+ on https://securityheaders.com:

script-src 'self' https://p.trellocdn.com https://alcdn.msauth.net https://cdn.msftauth.net https://login.microsoftonline.com https://login.live.com https://*.trello.com https://*.asana.com https://appsforoffice.microsoft.com;

In my QA testing, everything seems to be humming along with my A+ except that I started noticing my add-in failing to load sometimes. Pop open the F12 developer tools and viola, a MicrosoftAjax.js error that is failed to load because of my CSP. So, I added it:

script-src 'self' https://p.trellocdn.com https://alcdn.msauth.net https://cdn.msftauth.net https://login.microsoftonline.com https://login.live.com https://*.trello.com https://*.asana.com https://appsforoffice.microsoft.com https://ajax.aspnetcdn.com;

That seemed to make it happy, and my QA succeed so I published. I had figured it must be something in my code, but I did not find Ajax in any of my dependencies or my code base, so I gathered it was from office-js (rightly so), but no big deal, right. I did an NPM UPDATE and grabbed another coffee.

But then users started to contact me that they were unable to use the add-in. Why? Well, turns out in certain conditions my code did something that would fail. And after a lot of troubleshooting, I start to see this error around areas where I am getting mailbox session data:

Cannot read properties of undefined (reading ‘cannotDeserializeInvalidJson’)
at Sys.Serialization.JavaScriptSerializer.deserialize

So, oh boy… 2015 wet sock moment… the Microsoft Office JS library apparently injects the MicrosoftAjax.js library right into your taskpane page and uses it for JSON.parse(). What?

Send to Planner (and my other add-ins) were all showing this. And I had NEVER seen this Ajax error before — so it was new. I scoured the web, asked my new buddy Chat GPT and… long story short, because this was something new… What did I do… Pen Testing… Yes, and what did I recently change… Oh, yeah, my CSP. Yes… more digging, trial and error:

script-src 'self' 'unsafe-eval' 'unsafe-inline' https://p.trellocdn.com https://alcdn.msauth.net https://cdn.msftauth.net https://login.microsoftonline.com https://login.live.com https://*.trello.com https://*.asana.com https://appsforoffice.microsoft.com https://ajax.aspnetcdn.com;

Well, now I only have an A on security headers, and I get a nice little blurb about:

This policy contains ‘unsafe-inline’ which is dangerous in the script-src directive. This policy contains ‘unsafe-eval’ which is dangerous in the script-src directive.

Inline is one thing and good use of input sanitization with DomPurify (thanks MichaelZ), corner cases that. But unsafe-eval? I confirmed – on/off/on/off – yep… it needs to be there.

Anyway, it was a long journey – many hours – and I am wiser for it. I know this is probably because OfficeJS still supports versions back to Office 2013 and Edge before it went the path of the Chromium, but I hope maybe it can move away from needing eval() code and the MicrosoftAjax.js library in the near future and I can add a (+) back to my security posture.

Hopefully, this post will help others from tripping up on this as they lock things down.

403 Error with Planner API

I am posting this here as it is a vexing issue, and I have received no feedback or guidance on it.

If you use Power Automate with the Planner Graph API or you have developed a solution that used the Planner Graph API to create tasks you will be affected by this problem:

https://feedbackportal.microsoft.com/feedback/idea/91794c73-0e87-ef11-9442-6045bdb4f28f

Essentially, the issue is that the new PREMIUM Plans that you can create from Teams Planner App (and soon the updated Planner Website), are not fully supported in the Graph API.

When you try to POST (create) a task for example you get a 403 error. This would imply that you need some type of Graph API Permission in your Entra ID Registration, but there is NO DOCUMENTATION on this at all.

If you or your organization use Microsoft Planner and have recently upgrade to Premium plans, and you automate your plans, please UP VOTE the item reference above.

If you are using Power Automate, here is a simple one I created. If a task is created and assigned to you anywhere in Planner, it will create a new task for you on a specified plan in a specified bucket.

  1. If I run this, it creates a new task on the Plan: “Starts of Basic”
  2. If I upgrade the plan to PREMIUM and then go to any other plan and create a task, and the automation runs, this is what I get:

This is the error you get:

{
"error": {
"code": "ArchivedEntityCanNotBeUpdated",
"message": "Archived entity can't be modified.",
"innerError": {
"date": "2024-10-22T13:33:52",
"request-id": "f6cbf471-eead-49e8-944c-c3d9f9b00147",
"client-request-id": "f6cbf471-eead-49e8-944c-c3d9f9b00147"
}
}
}

Solving Outlook JS Email Reply Parsing with ES6 Class | Example Included

In working with Send to Trello, Send to Planner and Send to Asana, I found one of the most complicated tasks in Outlook JS is trying to determine where an email reply ends, and the original message begins in order to get the latest response in a Message.Read scenario.

Seems simple enough that you look for the break between the messages, our eyes pick that up fairly easily. I have not tried AI yet, but that is a next step for sure.

However, wanted to share what I have found works in most cases. I created an ES6 class OutlookEmailBodyParser. And you use it like this:

const emailBody = new Promise((resolve, reject) => {
try {
Office.context.mailbox.item.body.getAsync(type, (result) => {
if (result.status === Office.AsyncResultStatus.Succeeded) {
resolve(result.value);
} else {
reject(result.error);
}
});
} catch {
reject("Unable to get email body text.");
}
});
const parser = new OutlookEmailBodyParser(emailBody);
return parser.getLatestResponse();

And here is the class that does all the work:

export default class OutlookEmailBodyParser {
/** @type {String} */
#body = null;
/**
* Creates an instance of the Outlook Email Body Parser
* Next you call:
* – getLatestResponse() to get the most recent message
* @param {String} body
*/
constructor(body) {
this.#body = body;
}
/**
* Returns the latest response
*/
getLatestResponse = () => {
const lines = this.#body.replace("/\r/g", "\n").split("\n");
// any line that starts with a word, a color and a space, like From: , to: , Cc:, Date:
const prologLine = /(^[A-Z]{2,8}:\s)/i;
// any line that ends with a colon
const lineEndsWithColon = /^.+(:)$/;
// covers general patterns of first.last@email.domain
const emailRegex = /[a-zA-Z0-9._-]+@[a-zA-Z0-9-]+\.[a-zA-Z]{2,6}/;
// covers ——-, _______, —–original message—-, _______PREV______
const breakRegex = /^([-_]+(\w{0,15}(\s|\s{0})){0,3}[-_]+)$/;
// looks for a 4 digit number on the line, we grab it and see if it looks reasonable
// meaning in compare is the 4 digit number starting with 19 or 20.
const containsYear = /([/\s][0-9]{4})/;
// looks for a time in formats 4:44, 16:44, 4:44 AM, 4:44 PM
// we then look at capture groups to verify in range
const containsTime = /\s([0-9]{1,2}):([0-9]{2})(\w{2}|\s\w{2}|:[0-9]{2}|.{0})/;
// — START —
var breakOnLine = "";
var fFoundLineBreak = false;
var candidateLines = 0;
var prevLine = "";
for (const line of lines) {
var gmailFoundCount = 0;
var outlookFoundCount = 0;
if (breakRegex.test(line)) {
fFoundLineBreak = true;
candidateLines++;
prevLine = line;
continue;
}
if (emailRegex.test(line)) {
gmailFoundCount++;
outlookFoundCount++;
}
if (prologLine.test(line)) outlookFoundCount++;
if (lineEndsWithColon.test(line)) gmailFoundCount++;
if (containsYear.test(line)) {
const year = containsYear.exec(line);
if (year.length === 2) {
const num = Number.parseInt(year[0].trim());
if (!Number.isNaN(num) && num > 1900 && num < 2100) {
gmailFoundCount++;
outlookFoundCount++;);
}
}
}
if (containsTime.test(line)) {
const time = containsTime.exec(line);
if (time.length >= 3) {
const hour = Number.parseInt(time[1].trim());
const min = Number.parseInt(time[2].trim());
if (!Number.isNaN(hour) && !Number.isNaN(min) && hour >= 0 && hour <= 23 && min >= 0 && min <= 59) {
gmailFoundCount++;
outlookFoundCount++;
}
}
}
if (fFoundLineBreak) outlookFoundCount++;
////////////////////////////////////
// VALIDATE OUTLOOK
////////////////////////////////////
if (candidateLines >= 1 && outlookFoundCount > 2) {
breakOnLine = prevLine;
break;
}
////////////////////////////////////
// VALIDATE GMAIL
////////////////////////////////////
if (gmailFoundCount >= 4) {
breakOnLine = line;
break;
}
if (candidateLines === 1 && gmailFoundCount === 1) {
breakOnLine = prevLine;
break;
}
////////////////////////////////////
// reset
////////////////////////////////////
if (candidateLines === 3) candidateLines = 0;
if (outlookFoundCount >= 2) candidateLines++;
if (gmailFoundCount === 3) candidateLines++;
if (candidateLines === 1) prevLine = line;
fFoundLineBreak = false; // must set here
}
var latestResponse = this.#body;
if (breakOnLine) {
const pos = this.#body.indexOf(breakOnLine);
latestResponse = this.#body.substring(0, pos);
}
// return
return latestResponse;
};
}

Send to Planner v1.04 is Released

I have been absent from my blog for a while as I have been busy working on Kryl Solutions add-ins and Power-Ups. It has been an enjoyable experience to really delve into the world of JavaScript (ES6) and get creative building things.

One of my favorite projects has been Send to Planner. I just released version 1.04 and after 6 months of being in the marketplace, the reception and stats for this add-in are amazing. It has quickly become my fastest growing integration.

With v1.04, I have added checklists. Now, you can do nearly everything with task from Send to Planner: create new tasks in any plan and bucket, add to existing tasks as comments, add start and due dates, update priority, update selected categories, update the task progress, add/remove user assignments, and now, add, remove or update checklist items.

If you use Microsoft Planner for managing your projects, and you deal a lot with incoming email driving or updating your task workflow, you really should check Send to Planner out and let me know what you think.

Send to Planner Add-in Published

Right before the holidays I got my latest solution published to AppSource: “Send to Planner.”

This add-in is based on the “Send to Trello” Add-in and has a very similar look and feel. It was an unexpected side bar in my solutions development. A Send to Trello user contacted me because they also used Planner and were unable to find an effective solution that just did the basic “send an email to Planner” for free. While this has premium features to do much more, like keeping responses to the same email with the original task, the basic functionality will likely meet most casual use.

This was fun to create but also vexing at the same time. I am not sure where I got the idea, that there was an ability to get a front-end access token of the user running the Office add-in (getAccessToken). But turns out this is called an “on behalf of” flow that requires a complex manifest setup and you must send the customer email information to your backend web service to make the call “on behalf of” the user to Graph API. There are several options, but I had to go with the OAUTH flow using displayDialogAsync(). The AppSource (GDRP/Privacy) requirements for a backend data flow was more work and upkeep than I care for (cost and time wise). It is a headache I did not want.

So, I used the MSAL flow to pop an authentication dialog and then call the Graph API from the front-end to write to Planner. I already have users complaining that “it pops up the authentication dialog too much.” This is even though it does not require the entire authentication flow, it just pops up and then goes away.

I try the ssoSilent from the task-pane side and when/if that fails, I pop the dialog and do it there. The issue is because I do not own the frame of the Office task pane my origination domain is not correct. MSAL rejects the Silent SSO attempt. Ergo, it must pop open the dialog every time to get ssoSilent and refresh the token that way. A tad annoying.

I am not sure if I am missing something, but it would be nice to be able to request a front-end token with the proper scopes to do this, or a way to call ssoSilent from a task pane. But for now – this is what it is. Another annoying fact is that the token I get from MSAL, although it is refreshable (with ssoSilent), it lasts only 1 hour before it must be refreshed. So, 99% of the time a user clicks “Send to Planner” they see the dialog flash.

Either way, the add-in is out there and consumable. Please check it out!

Autolaunch and Outlook on Windows

I have been updating my 5entences add-in install for use in the AppSource/App Center for Office, plus adding an auto-summarization capability via OpenAI. 😉 COMING SOON!!!!

Originally, this add-in was using the Dialog API inside the “original” OnSend event. This was a bit nifty as it presented the user with a blocking popup that would allow them to manage everything in one place. It told them, they had more than 5 sentences, and would suggest they go back and fix it, or go ahead and send it. But that type of event is NOT supported in the App Store.

Enter Smart Events, and with it specifically the OnMessageSend event. The adventure began…

I have an Office 365 account and installed Office 365 from my portal.office.com page, made sure I was updated, and everything was looking good, except that:

  • When I have my personal Outlook.com email address attached to Outlook, I cannot debug add-ins in Outlook full client on Windows.
  • Even when I did remove my Outlook.com email address, my events were still not firing.
  • Everything worked great in Office on the Web.

So, I proceeded with updating the code using Office on the web to debug, giving up on Outlook full client in the interim. Once I got everything working well in Outlook on the Web, I went back to Outlook on Windows and began to lose my hair.

I quickly discovered that my ES6 code in the command.js, were not working. As you dig into the documentation you find that the WebViewURL and the override for JSRuntime, sharing the same file become an issue. It turns out that even with the WebView2 control installed on my box, and even though I have the latest version of Office 365 full client installed, my JSRuntime code reverts to Trident+ (IE11). I refactored my code to:

  • stop using const, instead var everything.
  • stop using => arrow operators, reverting to full function()
  • stop using async/await, and built pyramids of doom
  • stopped using Promises() even

The reason I reverted, is because I pulled out all transpiling and polyfills because they SLOW down my add-in code, make it too large and it impacts my already overwhelmed server. I also like to remain as pure JavaScript ES6 as I can. I am a bit puritanical, I guess. 😆

But even with all that it did not work. So, line by line I went and found the first problem:

/**
* Ensures the Office.js library is loaded.
*/
Office.onReady((info) => {
/**
* Maps the event handler name specified in the manifest's LaunchEvent element to its JavaScript counterpart.
* This ensures support in Outlook on Windows.
*/
if (Office.context.platform === Office.PlatformType.PC || Office.context.platform == null) {
Office.actions.associate("onMessageSendHandler", onMessageSendHandler);
}
});
view raw onready.js hosted with ❤ by GitHub

That code seemed to work, but the add-in would just hang telling me “…it is taking too long… Try Again.” I resolved it by doing this:

/**
* Checks to see if we are running in Windows Outlook
* @returns {Boolean}
*/
function isPC() {
try {
if (Office.context.platform === Office.PlatformType.PC || Office.context.platform === null) {
return true;
} else {
return false;
}
} catch {
return false;
}
}
if (isPC() === true) {
Office.actions.associate("onMessageSendHandler", onMessageSendHandler);
} else {
Office.onReady(function () {});
// Everything below is for OTHER (non-PC) clients per older constructs. Not certain
// if any of this is needed except for COMMANDS (rather than EVENTS), so eventually
// if commands are added, we have this in place…
var g = getGlobal();
// The add-in command functions need to be available in global scope
g.onMessageSendHandler = onMessageSendHandler; // on send event
}
/**
* OnSend event triggered
* @param {Office.AddinCommands.Event} event
*/
function onMessageSendHandler(event) {
Office.onReady(function () {});
Office.context.mailbox.item.body.getAsync(Office.CoercionType.Text, { asyncContext: event }, function (asyncResult) {
var body = asyncResult.value;
var event = asyncResult.asyncContext;
// … more code here …
});
}
view raw commands.js hosted with ❤ by GitHub

Essentially, I had to put the associate at the root of the file. Without the PC check, this would bomb on Office online and the Mac, so I gated that with the function you see: IsPC(). And Office online needs the Office.onReady() in the root to work effectively, so you see that there. But putting Office.ready() in the root broken Outlook on Windows, but also ignoring it/not using it, gave me the same problem. I discovered that if I put it in the event activation itself, as you see, I was finally able to get into my event handler and execute and Office.context (line of code). However, deeper in my code, it was STILL failing.

Debugging my code further, I had a LOT of Regex to determine what was going on in the body of the email. Introduce Trident+ and IE11 (es5), and you realize that “look heads” and a lot of really cool stuff I have grown accustomed to do not work. So, I struggled and struggled, until I just sent to my friend ChatGPT and asked it to produce me IE11 compatible Regex for each of my Regex statements. And then viola, my add-in was fully functional on Windows and Office online.

I am still a hacker by nature – what works, works. And I have converted from Office Add-ins written in C# with Visual Studio Tools to Office to this OfficeJS paradigm. I spend a lot of time reading the documentation and I SWEAR it tells me that with my version of Office 365 and having the WebView2 control installed, I should not be reverting to IE11 (Trident+) for Smart Events:

Microsoft® Outlook® for Microsoft 365 MSO (Version 2308 Build 16.0.16731.20052) 64-bit

But maybe it is buried in there somewhere that on Windows/PC, it always uses IE11 for Smart Events. Either way, to make sure you support 99% of the market out there, I guess you need to write you code like this or use polyfills and transpile. Bottom line is I got it to work. If anyone else is having similar issues, hopefully this helps. If anyone has an alternative or can point to something I am doing wrong (other than using polyfills and transpile my code), I am open to suggestions.

Excel Send to Trello

Well, that was fast. My latest add-in is published. Excel Send to Trello.

As per my previous post, the thing I found most interesting was how Excel full client seems to fail if you configure your server .htaccess file to prevent caching. Well, I found out that my Outlook Send to Trello add-in actually had the same problem too. The Outlook client just happened to refresh this morning and my icon disappeared there too. In Office online it seems to work, but in the full client you cannot seem to force the client to NOT cache. I see my files all pulled down locally in the Wef folder and my concern is that when I update the add-in it will not go get the latest every time… I have actually seem and beat my head over this problem a few times. But the solution there for anyone working on a BETA site for example making changes and all of a sudden the full-client stops refreshing your updates, this is a good article to keep handy.

But when Wef strikes in production, I have found customers are not so excited to blow away this folder and find all their add-ins, preferences, stored cached credentials and other goodies for each and every add-in are gone. Ergo why I added the no-cahce to the .htacess. Oh well. 🙁

Also, just to share something else as I am delving more and more into publishing add-ins for real. As an Office Developer, in the traditional sense (aka boomer 😛, VBA/VSTO/COM), there are aspects of living in an HTML web world that I still learning (although this is an old one it comes up now and again because I forget something).

You have to worry about various attack vectors and sanitizing HTML strings. There are LOTS of libraries and solutions out there and Mozilla even has documented a possible standard supported in several browsers, but not all. It is a tricky thing because some sanitizers do too much or not enough, and then you also rely on a dependency which has now burned me more often than just owning things that might be a hundred lines of code for my own common library of goodies.

So, I have created my own based on various library implementations, and found the best option is to escape most of the stuff you find “injected” rather than remove it.

/**
* Sanitizes the string for possible malicious values
* @param {String} string
* @returns {String}
*/
static sanitizeString = (string) => {
try {
string = string.replace(/(javascript:|onerror)/gi, "");
string = string.replace(/undefined/gi, "");
string = string.replace(/<script/gi, "&lt;script");
string = string.replace(/<iframe/gi, "&lt;iframe");
string = string.replace(/<object/gi, "&lt;object");
string = string.replace(/<embed/gi, "&lt;embed");
string = string.replace(/<applet/gi, "&lt;applet");
string = string.replace(/<form/gi, "&lt;form");
string = string.replace(/<meta/gi, "&lt;meta");
string = string.replace(/<link/gi, "&lt;link");
string = string.replace(/<a\s/gi, "&lt;a ");
string = string.replace(/<img\s/gi, "&lt;img ");
string = string.replace(/="/gi, "&#x3D;&#39;");
string = string.replace(/='/gi, "&#x3D;&#39;");
string = string.replace(/=`/gi, "&#x3D;&#x60;");
string = string.replace(/\/>/gi, "&#x2F;&gt;");
return string;
} catch (e) {
return `[[SANITIZED STRING MALFORMED: ${e}]]`;
}
};

The important thing is that in the web world, anytime you take data from one service to another, or take input in a field, or grab input from some element on a page and insert it back into another element in your code, there is a hack waiting to happen if you do not sanitize.

New Excel Add-in & Side Loading

I recently published a new Excel Add-in called “Excel Send to Trello.” It is a pretty nifty add-in that will take an Excel sheet full of names, descriptions, and dates and build a bunch of Trello Cards on a specific list in Trello for you. It is just a start (v1.0) and has many features to come based on popularity.

But when I submitted the add-in to AppSource they came back to me with an issue that I was not seeing. Specifically, that the icon on the Ribbon was showing the default add-ins image and not my icon. I did not see this on my “beta” test site that I was using but had sent them a link to my manifest with was right to my production published version. The issue it turns out after a lot of testing was the .htaccess on my production site and the ONLY difference was this line:

<IfModule mod_headers.c>
    Header set Cache-Control "no-store, no-cache, must-revalidate"
</IfModule>

The problem it turns out is that Excel (full client) cannot read your icon without caching it. This works FINE in Excel Online. Oh well. So, I cannot prevent my add-in JS files from being cached which it turns out is a huge problem when I push an update it can take days/weeks for the Office application to expire its own cache. Unless someone know a way around this…

Anway, the way I discovered this was via sideloading the manifest pointing to my production site. And it also turns out I had forgotten how EASY Outlook makes sideloading. In Excel (full client) it is not that easy, because you do not have the option to point to your own manifest, except in Excel Online. Then I recall some work a colleague Marty Andren and I worked on many, many years ago, called the Web Add-in Side loader. And low and behold, it still works like a charm.

I was able to sideload in Excel using the command line tool, then I saw the problem. That is where I began the file-by-file comparison “Beta” to “Production” and found the difference mentioned above.

Anyway, it was an adventure, with a blast from the past! And keep an eye out, my new Excel Add-in will hopefully be published any day now.

Outlook Signature Add-in

I recently got a question about this add-in created as a sample by the Microsoft Office Developer Team. It is provided as a sample and is a challenging bit of code to follow. It all started because I was trying to provide some advice to someone who is new to the OfficeJS world (but not Office development in general). They wanted to build a cross-platform add-in for a cause. After tinkering with it for a few hours, I managed to get it to work. But then I looked it over and it was quite a bit of tinkering on my part.

In most cases I create Add-ins based on React, but because I have done a lot of Trello development of late, I have gotten into more of a pure ES6 vibe. The code provided in the add-in as I stated was hard to follow because I think I am losing my jQuery and ES5 JavaScript skills.

Anyway, as I said, I spent a few hours today and worked on getting it into ES6 format by moving most of the code into classes, removing redundant code, removing redundant HTML pages, consolidating CSS and adding a lot of additional JSDoc comments throughout the code.

Hopefully, this version is a bit easier to follow for those that develop in a more modern ES6 style.

NOTE: This looks and behaves exactly the same, but I have not thoroughly tested it. So, it might be a bit rough here and there. If you encounter issues please let me know. 😁

Determine MIME type from base64

In writing my new Outlook Add-in (Send to Trello), I got stuck on attachments. The first version did not include an attachments option because of two unique problems that compounded each other:

  1. The Office Add-in API no longer provides a Media Type/MIME Type with an attachment request. I am able to get the “blob()” from Office, but other than the file extension there is not a way to determine the type. But sometimes a file does not have an extension, or the extension is wrong, etc.
  2. The Trello API will not let you upload without supplying a MIME type, you cannot just give it a Base64 string as an attachment and let them figure it out.

So, I found out something interesting while researching a workaround. Most every base64 string of a specific file type starts with the same “prolog” of text. Using this, combined with the fallback of the file extensions, I was able to get attachments to work (for the attachment types supported by Trello). So, v1.02 will now include attachments.

Anway, as for the workaround I found, this might be ugly, but wanted to share it anyway:

/**
* Returns the data type based on the base64 string
* @param {String} base64String
* @param {String} fileName
* @returns {String}
*/
detectMimeType(base64String, fileName) {
var ext = fileName.substring(fileName.lastIndexOf(".") + 1);
if (ext === undefined || ext === null || ext === "") ext = "bin";
ext = ext.toLowerCase();
const signatures = {
JVBERi0: "application/pdf",
R0lGODdh: "image/gif",
R0lGODlh: "image/gif",
iVBORw0KGgo: "image/png",
TU0AK: "image/tiff",
"/9j/": "image/jpg",
UEs: "application/vnd.openxmlformats-officedocument.",
PK: "application/zip",
};
for (var s in signatures) {
if (base64String.indexOf(s) === 0) {
var x = signatures[s];
// if an office file format
if (ext.length > 3 && ext.substring(0, 3) === "ppt") {
x += "presentationml.presentation";
} else if (ext.length > 3 && ext.substring(0, 3) === "xls") {
x += "spreadsheetml.sheet";
} else if (ext.length > 3 && ext.substring(0, 3) === "doc") {
x += "wordprocessingml.document";
}
// return
return x;
}
}
// if we are here we can only go off the extensions
const extensions = {
xls: "application/vnd.ms-excel",
ppt: "application/vnd.ms-powerpoint",
doc: "application/msword",
xml: "text/xml",
mpeg: "audio/mpeg",
mpg: "audio/mpeg",
txt: "text/plain",
};
for (var e in extensions) {
if (ext.indexOf(e) === 0) {
var xx = extensions[e];
return xx;
}
}
// if we are here – not sure what type this is
return "unknown";
}