Converting a SharePoint document library file link to the link’s target

sharepoint document library file link

Brief

To have a SharePoint document library containing several documents and links to documents in other libraries – allowing documents from a central repository to be linked to from several places. 

Using a SharePoint Framework application, display a list of these documents and document links with “open in browser” links. 

Problem 

If you use the SharePoint APIs to retrieve the contents of the SharePoint library, these links are also included in the response. However, the serverRelativeUrl of these links isn’t that of the linked file, as they are treated as a discrete file type. Your browser will download these, rather than opening the linked file in the browser. 

For example, if your document library “Project Documents” contains a link to a file “My Central Document.docx”, when this link’s serverRelativeUrl is followed, a file “My Central Document.url” is downloaded, this file is a web shortcut. 

Solution

To overcome this, is it necessary to have the JavaScript to retrieve any .url files and interrogate the content looking for the linked file’s location, then return that url to be used instead.

To retrieve the file, we use the fetch api. This returns a Response object that can be used to retrieve the files body content.

let fetchedFile = await fetch(serverRelativeUrl);

An issue I discovered when dissecting this problem was that if you access the fetchedFile response object in the incorrect order, you’ll receive an exception:

Uncaught (in promise) TypeError: Failed to execute 'getReader' on 'ReadableStream': ReadableStreamReader constructor can only accept readable streams that are not yet locked to a reader

I received this error when trying to get the body’s reader after accessing the blob of the response:

var blob = await urlFile.blob();
var reader = urlFile.body.getReader(); <-- Exception thrown here.

Once this problem was resolved and the responses’ body reader had been accessed, the data could be read from the file. As the rad function returns a Uint8Array object, a new TextDecoder must be used to decode the content and append it onto the currently retrieved body until the reader returns that it is done.

while (!done) {
let readData = await bodyReader.read();
// Decode the Uint8Array
bodyContent = bodyContent + new TextDecoder("utf-8").decode(readData.value);
done = readData.done;
}

Finally, as the body of the downloaded file looks like this:

[InternetShortcut]
URL=https://mytennant.sharepoint.com/sites/MySite/MyDocuments/notes.txt

We must use a simple regular expression to extract the actual link target before returning it.

Final Solution 

When we put all these code snippets together, the final solution is as follows: 

/**

* Converts a .url document library file link into the link's target.


*
@param serverRelativeUrl Url of the .url file.

*/

private async getLinkTarget(serverRelativeUrl: string): Promise<string> {
// Use fetch to retrieve the file
let fetchedFile = await fetch(serverRelativeUrl);
let bodyReader = fetchedFile.body.getReader();

let bodyContent = '';
let done = false;

// Read the file content until it's all been read
while (!done) {
let readData = await bodyReader.read();
// Decode the Uint8Array
bodyContent = bodyContent + new TextDecoder('utf-8').decode(readData.value);
done = readData.done;
}

// Because the file has multiple lines and URL=(the actual url>

// we need to regex to extract the correct url.

let regex = new RegExp('URL=(?.*)');
// Use type any as exec doesn't by default include 'groups'
let regexData: any = regex.exec(bodyContent);
let url = regexData.groups.Url;

return url;
}