Pnpjs: Graph.me.drive.getItemById(<Id>).getContent() => Error : Failed to fetch

Created on 27 May 2020  路  10Comments  路  Source: pnp/pnpjs

Hi All !

Category

  • [ ] Enhancement
  • [x] Bug
  • [x] Question
  • [ ] Documentation gap/issue

Version

Please specify what version of the library you are using: [ 2.0.5 ]
Please specify what version(s) of SharePoint you are targeting: [ SPO ]

Expected / Desired Behavior / Question

graph.me.drive.getItemById().getContent() returns me the content of the file

Observed Behavior

I have an exception with Error : Failed to fetch

Steps to Reproduce

Create a new SPFX Webpart
Install pnp graph
Execut the following code with a valid file path

this.props.graphClient
 .api('/me/drive/root:/MyFolder/MyFile.json')
 .version('v1.0')
 .get((error, response: any, rawResponse?: any) => {
   graph.me.drive.getItemById(response.id).getContent()
     .then(file => {
     })
     .catch(err => {
       debugger;
     });
   });

I correctly set up permissions with Files.Read and User.Read.
I correctly obtain file infos.
I correctly obtain a working download url with field @microsoft.graph.downloadUrl .

For information, I did not manage to have Microsoft Graph API working with Graph explorer with
/me/drive/root:/MyFolder/MyFile.json:/content
This call is endlessly loading with no answser or error.

More information

The underlying reason I tried to use this method is explained here.

I am trying to get a JSon file content stored in my Office 365 OneDrive to process it in an SPFX Webpart.
I tryed many method and none of them worked until now.
Any advice welcome to succeed in something I thought was trivial !

code complete enhancement

Most helpful comment

After testing this I am not sure how it can work with CORS. I have code now that auths, gets the url for the items "@microsoft.graph.downloadUrl" and I still get blocked by CORS because I am running an SP site. Using XMLHttpRequest wouldn't change that (we use fetch). So unsure what we can really do to help here. @JMTeamway did your code work and in what context?

Nevermind all that, with a little help from @juliemturner we got it figured out. This will be part of the next release.

All 10 comments

When calling with Graph Explorer :
https://graph.microsoft.com/v1.0/me/drive/root:/Myfolder/MyFile.json
=> I get a correct response.

When calling with Graph Explorer :
https://graph.microsoft.com/v1.0/me/drive/root:/Myfolder/MyFile.json:/content
=> I get in fact an error in the console due to this cancer called CORS !

Access to fetch at 'https://mytenant-my.sharepoint.com/personal/my_mail/_layouts/15/download.aspx?UniqueId=b4[...]b6b0&Translate=false&tempauth=eyJ0[...]Hlubz0&ApiVersion=2.1' (redirected from 'https://graph.microsoft.com/v1.0/me/drive/root:/MyFolder/MyFile.json:/content') from origin 'https://developer.microsoft.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

I don't have any clue on how I'm supposed to deal with CORS in this case ...

Ok, according to this article ( https://docs.microsoft.com/fr-fr/onedrive/developer/rest-api/concepts/working-with-cors?view=odsp-graph-online ), it is not possible to download a file content using /content because of the 302 redirection of this endpoint and CORS management it implies.

Instead, we should use property @microsoft.graph.downloadUrl that is an authanticated link to the file that does not triger a preflight CORS.

So, if pnpjs provide an API to get a file content, should it be able to get directly the content using this direct link ?

Or in my case, how could I get file content from direct url using pure Typescript ? I did not manage to do it yet ...

We do not, at this time, provide a wrapper to download a file from Graph.

As the article says you just need to get that downloadUrl and then make fetch call to get the contents. The answer has nothing really to do with being in TypeScript. I suspect you can use a simple fetch to get the file passing in that downloadUrl.

Is this an enhancement opportunity?

It defintely is. If I needed it, I can assume that others do ! :-)

Before the suggestion of @juliemturner (thanks, I'll give a try !), here is the solution I build.
I hope it can help anyone interested in this issue.

this.graphClient
      .api('/me/drive/root:/MyFolder/MyFile.json?select=id,@microsoft.graph.downloadUrl')
      .version('v1.0')
      .get((error, response: any, rawResponse?: any) => {
        if(response == null){
          reject("File not found");
        } else{
          // Download file and return content
          const xhr = new XMLHttpRequest();
          xhr.open('get', response["@microsoft.graph.downloadUrl"]);

          xhr.onload    = evt => { resolve(JSON.parse(xhr.responseText)); };
          xhr.onerror   = evt => { reject(evt); };
          xhr.ontimeout = evt => { reject(evt); };

          xhr.send();
        }
      });

I don't think this kind of code should be used to implement the /content endpoint, as Microsoft designed it to behave in a certain way ( 302 redirect ), I imagine pnpjs should implement it the same way.

May be pnpjs Graph api could add another method, for example /contentAsText that would behave this way ...

I this this code addresses that issue. You can test using an extension method while we work to get it rolled into the library. Please let us know if it works as we haven't tested it thoroughly.

    public async getContent(): Promise<any> {
        const url = await this.select("id", "@microsoft.graph.downloadUrl")();
        return graphGet(url);
    }

After testing this I am not sure how it can work with CORS. I have code now that auths, gets the url for the items "@microsoft.graph.downloadUrl" and I still get blocked by CORS because I am running an SP site. Using XMLHttpRequest wouldn't change that (we use fetch). So unsure what we can really do to help here. @JMTeamway did your code work and in what context?

Nevermind all that, with a little help from @juliemturner we got it figured out. This will be part of the next release.

Thanks @patrick-rodgers and @juliemturner for the great work !

To answer your question, yes my code do work.
For what I understood, xhr do not leverage CORS mechanism, so I can download the expected content using the given url, while fetch do leverage CORS, and therefor do not allow me to get the result.

For the sake of science, I'd be interested on the way you solved this issue : the mecanism may be useable in other contexts where CORS is prowling around

The issue was not related to CORS specifically it was related to not adding the token to the call for the download URL...

Was this page helpful?
0 / 5 - 0 ratings