Fixing SPFx node-sass binding error on ADO release pipeline

When trying to run the gulp upload-to-sharepoint  encountered the following issue when creating a release pipeline for an SPFx web-part. There was a problem with no binding available for node-sass

[command]C:\NPM\Modules\gulp.cmd upload-to-sharepoint –gulpfile D:\a\r1\a\build\release\gulpfile.js –ship –username *** –password *** –tenant mckinseyandcompany –cdnsite sites/apps/ –cdnlib ClientSideAssets
2019-06-12T14:51:53.5954467Z [14:51:53] Working directory changed to D:\a\r1\a\build\release
2019-06-12T14:51:54.5490645Z D:\a\r1\a\build\release\node_modules\node-sass\lib\binding.js:15
2019-06-12T14:51:54.5497252Z throw new Error(errors.missingBinary());
2019-06-12T14:51:54.5498022Z ^
2019-06-12T14:51:54.5498662Z
2019-06-12T14:51:54.5499258Z Error: Missing binding D:\a\r1\a\build\release\node_modules\node-sass\vendor\win32-x64-48\binding.node
2019-06-12T14:51:54.5499538Z Node Sass could not find a binding for your current environment: Windows 64-bit with Node.js 6.x
2019-06-12T14:51:54.5499731Z
2019-06-12T14:51:54.5499883Z Found bindings for the following environments:
2019-06-12T14:51:54.5500034Z – Windows 64-bit with Node.js 8.x

and the error was actually staring us in the face – “binding available for Node 8″……..

The solution, just like for the build process, you have to add an agent task to ensure the correct version of node is used for the release process.

Correcting SPFx gulp –ship Uglify Errors: Unexpected token: punc (()

We came across the following problem when trying to execute a gulp --ship on out SPFx development


[15:03:34] Starting subtask 'webpack'...
[15:03:49] Error - [webpack] 'dist':
list-view-demo-webpart-web-part.js from UglifyJs
Unexpected token: punc (() [list-view-demo-webpart-web-part.js:962,7]

In researching this issue it turns out that this issue stems from a problem with the webpack uglify plugin (uglify-webpack-plugin) which historically does not work with ES6 code.

From what I read, it seems like the current SPFx 1.8 does use the correct version of webpack and the uglify plugin to avoid this issue but it was still coming up.

We solved the issue by implementing a suggestion to a related issue on github

https://github.com/SharePoint/sp-dev-docs/issues/2782

The key was presented in one of the responses to the issue

https://github.com/SharePoint/sp-dev-docs/issues/2782#issuecomment-475519680

By replacing the uglify plugin with the terser plugin for webpack, the issue was resolved and we were able to Build and deploy.

How Windows 10 virtual desktops help me with Office 365 development

In this article I will show how the Windows 10 virtual desktops help keep my brain organized for Office 365 development. It allows me to keep my Chrome Primary work profile for email etc completely separate from my developer tenat Chrome profile. It helps keep me more organized when working on multiple projects as well.

Background

Anyone who has more than one development and primary Office 365 account has come across the age old problem of which environment am I currently working in. Using Chrome profiles as a number of people have blogged about has been a huge help in this. Having a Chrome profile for dev tenant and profile for primary account (work email etc) is a huge help.

But the downside is many instances of chrome floating around and when you click on a link in Outlook it opens in the last Chrome window you were working on – which is frustrating at the best of times when your dev tenant says you dont have access to your primary email (duh) and then you have to hunt down the primary profile window and go back to outlook and start again.

Windows 10 Virtual Desktop

This is where the virtual desktop has really become my best friend for Office365 development. In the latest version of Windows 10 you can easily make a virtual desktop by adding the “Show Task View button” from the context menu when right clicking on the windows task bar.

Clicking on the Task icon brings up the option to create a new virtual desktop and also a view of some of things you have worked on recently.

Opening a new virtual desktop everything is blank and you can open Chrome and go to your dev chrome profile happily.

Switching between desktops

You can go through the process of Task View and clicking on the desktop you want

OR

You can use CTRL-WINDOWSKEY-Left/Right to navigate back and forth between the desktops. They don’t cycle but if you only have two then Left or Right isn’t that hard 🙂

Clicking a link in outlook

If I am working in desktop 2 (O365Dev) and I go back to Desktop 1(primary O365), clicking a link in the primary O365 Outlook opens the link in the primary desktop chrome profile. Always.

YAY 🙂

Mentally this also helps me keep my Director day job (primary O365) and dev environment (xomino365) separate and makes life easier.

It’s not for everyone but really helps me 🙂

Extra Bonus shortcut

I discovered by accident (as you do) that you can create a new Virtual desktop using the CTRL key and a three-finger down swipe on the laptop.

 

 

Building Office Add-ins using Office.js – the book, the website

I wanted to show some love for the new website and book by Michael Zlatkovsky. Michael is a Software Developer and Program Manager in the Microsoft Office Extensibility team. I met Michael at the MS MVP Summit back in October and we had a great conversation or two about Office Addins and their future development.

So Michael’s new leanpub book is called Building Office Add-Ins using Office.js and can be purchased through leanpub. The new supporting website http://buildingofficeaddins.com/ provides some of the key information from the book and gives a nice overview thereof while introducing the reader to the more technical aspects of using Office.js.

Michael’s a great guy and this is a very valuable resource for Office Add-In developers. I heartily recommend it.

Sharing data between Office Add-Ins using localStorage

In this article I will show how the underlying dependence on the browser (in this case IE11 in Windows) allows us to pass data between Office Add-Ins through the use of HTML5 localStorage. At this point this is a theoretical post as I haven’t thought of a good use case yet, I am sure I will at some point…

Introduction 

Earlier this year I created a demo for the Salesforce global conference (Dreamforce) which showed how to create an Office Add-In to extract and manipulate Salesforce data from within an Office Add-In (you can see the presentation here). For the main demo I actually created one “Web Page” and reused it in the context of Word and Excel. In this article I will demonstrate how we can easily set a localStorage value in IE11 or in an Office Add-In and have that readable in the other client environments.

localStorage

localStorage is part of the HTML5 Web Storage API which allows the permanent storage of string values in a browser. Like cookies the ability to read and write is tied to a domain. You can get/set values very easily by using the following notation:

  localStorage.setItem('name', 'Marky');
  localStorage.getItem('name'); //Marky

or even as simple as

  localStorage.name = 'Marky';
  localStorage.name; //Marky

The Embedded Browser 

When utilizing an Office Add-In within the Office client apps we are really using an embedded browser session within the context of the external client. In the case of Windows it is Internet Explorer. What this means though is that when we access a web domain, and set a localStorage value, that value is always available to us, when using Internet Explorer on that domain. Let me show you.

The Example

Here is my application running hosted on Azure in the xomino365.azurewebsites.net domain

ls1

Through the console I created a new value

  localStorage.Marky = "This value set in IE11";

and as you can see below that is now accessible from localStorage

ls2

If I now load up my Office Add-In within Word we can see (using the F12 tool) that  it is the same URL (same web page). Because it is in the Add-In “host_Info=Word” is added to the URL but other than that it is the basically same.

ls3

Looking at the localStorage value we can also see “marky”

ls4

We can set a value here in Word

ls5

and then open the Add-In in Excel and retrieve the localStorage values previously set

ls6

Caveat

If you have Word and Excel open you cannot set a localStorage value in one and have it picked up in the other, it seems that they only pick up the new values once they are opened. If you wanted to pass information between them in real time you could do that using WebSockets (something for a future demo).

Persistence between different Add-Ins

Now that I have set these values using the same application, we can also demonstrate that the principle is still applicable between different Add-Ins hosted in the same domain. In this example I have a different Add-In, still hosted on xomino365.azurewebsites.net (this time in Outlook). All the localStorage values are still available to me.

ls7

Conclusion

In this article we have seen how we are able to set a localStorage value in Internet Explorer and then Word and have those values available in Excel. We have also seen that these values are actually available in other Add-Ins as long as they are hosted on the same domain.

This capability is all due to the underlying fact that Internet Explorer is the browser used to create all the demonstrated functionality.

 

Speaking at Dreamforce next week :)

Next week I will be in San Fransisco for the Dreamforce conference – this will be the biggest conference I have attended, never mind spoken at and I am really excited to go 🙂

If anyone is going to be around at the conference or in the area – ping me on Twitter @MarkyRoden – be good to meet up 🙂

 

Unleashing the power with Salesforce and Microsoft Office 365 Add-ins

https://success.salesforce.com/MyAgenda?eventId=a1Q3000000qQOd9EAG#/session/a2q3A000000LBjBQAW

TIME & LOCATION

Moscone West, Developer Lightning Theater

Saving a word document directly from an Office Add-In into Salesforce.com

In this article I will demonstrate and discuss how to programmatically save a Word document directly into Salesforce.

Introduction

In previous articles we have looked at how to programmatically interact with a Word document from within an Office Add-In. In this case we will use the Office JavaScript API to access the file as a binary string and then convert it into a format compatible with Salesforce.com.

Turning your word document into a binary

This article in the Office dev center details how to Get the whole document from an add-in for PowerPoint or Word. We will use the bulk of that article up to the point where we send the slice. We are going to look at a modified sendSlice() function. The dev.office.com article details how you can access the binary contents of the word document, but falls short when it comes to getting the base64 encoded version of the file to be submitted to an external site. This is the complicated part !

Getting the right format for Salesforce

Unfortunately the salesforce documentation is not based around using JavaScript to access the REST APIs. It is based around using curl commands in UNIX. The sample page for Inserting or updating Blob Data explains how to upload a multipart message which includes both the meta-data values and the binary data for the file. For the record they also neglect to provide any guidance on how to get the binary of the file….!

Using the code posted on this stackoverflow post  we are able to turn a binary string into the necessary format to send the file to salesforce.

The final sendSlice() function is shown below. The significant thing to note is that the initial file must be sent to the ContentVersion sobject. Because this is a new document a parentId is not necessary and a ContentDocumentId is created. In a future article we will look at how to then associate this new file attachment is an existing object.

function sendSlice(slice, state) {
  updateStatus("In sendSlice");
  var data = slice.data;

  // If the slice contains data, create an HTTP request.
  if (data) {

    // Encode the slice data, a byte array, as a Base64 string.
    // NOTE: The implementation of myEncodeBase64(input) function isn't
    // included with this example. For information about Base64 encoding with
    // JavaScript, see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding.

    var u8 = new Uint8Array(data);
    var b64encoded = btoa(String.fromCharCode.apply(null, u8));

    var objectData = {
      "Description": "Demo Upload Directly from Word",
      "Title": "sample.docx",
      "ReasonForChange": "Initial Upload",
      "PathOnClient": "sample.docx"
    }

    var boundary = 'boundary_string_' + Date.now().toString();
    var attachmentContentType = 'application/octet-stream';

    // Serialize the object, excluding the body, which will be placed in the second partition of the multipart/form-data request
    var serializedObject = JSON.stringify(objectData);

    var requestBodyBeginning = '--' + boundary
        + '\r\n'
        + 'Content-Disposition: form-data; name="entity_attachment";'
        + '\r\n'
        + 'Content-Type: application/json'
        + '\r\n\r\n'
        + serializedObject
        + '\r\n\r\n' +
        '--' + boundary
        + '\r\n'
        + 'Content-Type: ' + attachmentContentType
        + '\r\n'
        + 'Content-Disposition: form-data; name="VersionData"; filename="filler"'
        + '\r\n\r\n';

    var requestBodyEnd =
        '\r\n\r\n'
        + '--' + boundary + '--';

    // The atob function will decode a base64-encoded string into a new string with a character for each byte of the binary data.
    var byteCharacters = window.atob(b64encoded);

    // Each character's code point (charCode) will be the value of the byte.
    // We can create an array of byte values by applying .charCodeAt for each character in the string.
    var byteNumbers = new Array(byteCharacters.length);

    for (var i = 0; i < byteCharacters.length; i++) {
      byteNumbers[i] = byteCharacters.charCodeAt(i);
    }

    // Convert into a real typed byte array. (Represents an array of 8-bit unsigned integers)
    var byteArray = new Uint8Array(byteNumbers);

    var totalRequestSize = requestBodyBeginning.length + byteArray.byteLength + requestBodyEnd.length;

    var uint8array = new Uint8Array(totalRequestSize);
    var i;

    // Append the beginning of the request
    for (i = 0; i < requestBodyBeginning.length; i++) {
      uint8array[i] = requestBodyBeginning.charCodeAt(i) & 0xff;
    }

    // Append the binary attachment
    for (var j = 0; j < byteArray.byteLength; i++, j++) {
      uint8array[i] = byteArray[j];
    }

    // Append the end of the request
    for (var j = 0; j < requestBodyEnd.length; i++, j++) {
      uint8array[i] = requestBodyEnd.charCodeAt(j) & 0xff;
    }

    //Create the new file in Salesforce
    var url = "https://yourdomain.my.salesforce.com/services/data/v37.0/sobjects/ContentVersion/"

    return $.ajax({
      method: "POST",
      url: url,
      processData: false,
      data: uint8array.buffer,
      headers: {
        "Content-Type": 'multipart/form-data' + "; boundary=\"" + boundary + "\"",
        "Authorization": "Bearer " + app.getCookie(app.addinName)
      }
    }).error(function (data) {
      updateStatus("FAIL: " + JSON.stringify(data))
    }).success(function (data) {
      updateStatus("Success creating ContentVersion: " + data.id)
      closeFile(state)
    });

  }
}

 

Conclusion

In this article we have seen an example of how we can extend the functionality of an Office Add-In to access the binary data of a word document and then save it to a cloud provider (in this case Salesforce).