node.jsmicrosoft-graph-apinode-request

base64 converted photo from Graph displays as broken image


I've racked my brain (and others) trying to get this to work. I am pulling a photo via MS Graph API - this part works fine. I am able to receive the data (as bytes). However, I can't get the image to convert properly to be attached as a file and posted.

I have read several postings on SO and GH as well as having tried ~10 different npm packages and flavors(btoa, atob, etc...out of desperation), including the JS example from the Graph docs. No solution has worked. The npm packages all produce different outputs from one another and none of them match the output when I take the photo and upload to an online base64 converter. In addition, if I take the online conversion and place the output string directly into the code, it works.

Here is the current iteration of my code. Any help would be appreciated.

var optionsPhoto = {
  url: "https://graph.microsoft.com/v1.0/me/photo/$value",
  method: "GET",
  headers: {
    Authorization: "Bearer " + token
  }
};

await request(optionsPhoto, function callback(error, response, body) {
  if (!error && response.statusCode == 200) {
    photoResponse.data = [
      {
        "@odata.type": "#microsoft.graph.fileAttachment",
        contentBytes: body.split(",").toString("base64"),
        contentLocation: "https://graph.microsoft.com/v1.0/me/photo/$value",
        isinline: true,
        Name: "mypic.jpg"
      }
    ];
    photoResponse.ContentType = response.headers["content-type"].toString();
    photoResponse.Base64string = (
      "data:" +
      photoResponse.ContentType +
      ";base64," +
      photoResponse.data[0].contentBytes
    ).toString();
  } else {
    console.log(error);
  }
});

The .sendActivity command takes the attachment only as seen below:

await dc.context.sendActivity({
  attachments: [
    { contentType: photoResponse.ContentType, contentUrl: photoResponse.Base64string }
  ]
});

Solution

  • When you request the photo's /$value, the response will simply be the image's raw binary. The request client however treats the body as a utf8 based string by default.

    In order to retrain the raw binary value, you need to explicitly tell request that you don't want this to happen. This is done by setting encoding: null. From the documentation:

    encoding - encoding to be used on setEncoding of response data. If null, the body is returned as a Buffer. Anything else (including the default value of undefined) will be passed as the encoding parameter to toString() (meaning this is effectively utf8 by default). (Note: if you expect binary data, you should set encoding: null.)

    The code would look something like this:

    var optionsPhoto = {
      url: "https://graph.microsoft.com/v1.0/me/photo/$value",
      encoding: null, // Tells request this is a binary response
      method: "GET",
      headers: {
        Authorization: "Bearer " + token
      }
    };
    
    await request(optionsPhoto, function callback(error, response, body) {
      if (!error && response.statusCode == 200) {
        // Grab the content-type header for the data URI
        const contentType = response.headers["content-type"];
    
        // Encode the raw body as a base64 string
        const base64Body = body.toString("base64");
    
        // Construct a Data URI for the image
        const base64DataUri = "data:" + contentType + ";base64," + base64Body;
    
        // Assign your values to the photoResponse object
        photoResponse.data = [
          {
            "@odata.type": "#microsoft.graph.fileAttachment",
            contentBytes: base64Body,
            contentLocation: optionsPhoto.url,
            isinline: true,
            Name: "mypic.jpg"
          }
        ];
        photoResponse.ContentType = contentType;
        photoResponse.Base64string = base64DataUri;
      } else {
        console.log(error);
      }
    });