Reactjs How to download file from Azure Storage Container - javascript

I am working on reactjs/typescript applications. I am trying to download some files from azure storage v2. Below is the sample path I am supposed to download files. In this path, enrichment is the container name, and the rest all are folders. I am trying to download the last modified file from reportdocument folder.
enrichment/report/SAR-1234-56/reportdocument/file1.docs
I tried something below.
#action
public async reportDownload(sarNumber: string) {
let storage = globals.getGlobals('StorageAccount03');
console.log(storage);
let containerName = globals.getGlobals('StorageAccount03ContainerName');
let marker = undefined;
let allUploadPromise: Array<Promise<unknown>> = [];
const config = {
path: `/Storage/getsastoken/?storageName=${storage}&containerName=${containerName}`,
method: "GET",
success: (url: any) => {
const containerURL: ContainerURL = new ContainerURL(
url,
StorageURL.newPipeline(new AnonymousCredential()));
const listBlobsResponse = containerURL.listBlobFlatSegment(
Aborter.none,
marker,
);
}
};
await handleRequest(config);
}
From here I am struggling to download the latest modified file from the above path.
can someone help me to fix this? Any help would be greatly appreciated. Thank you

It's better to use #azure/storage-blob library and then the code would be something like below instead of directly trying to call blob REST API like you were trying in your code which seems unnecessary reinventing the wheel. The library already does it for you. Refer this for details.
const { BlobServiceClient } = require("#azure/storage-blob");
const account = "<account name>";
const sas = "<service Shared Access Signature Token>";
const containerName = "<container name>";
const blobName = "<blob name>";
const blobServiceClient = new BlobServiceClient(`https://${account}.blob.core.windows.net${sas}`);
async function download() {
const containerClient = blobServiceClient.getContainerClient(containerName);
const blobClient = containerClient.getBlobClient(blobName);
// Get blob content from position 0 to the end
// In browsers, get downloaded data by accessing downloadBlockBlobResponse.blobBody
const downloadBlockBlobResponse = await blobClient.download();
const downloaded = await blobToString(await downloadBlockBlobResponse.blobBody);
console.log("Downloaded blob content", downloaded);
// [Browsers only] A helper method used to convert a browser Blob into string.
async function blobToString(blob) {
const fileReader = new FileReader();
return new Promise((resolve, reject) => {
fileReader.onloadend = (ev) => {
resolve(ev.target.result);
};
fileReader.onerror = reject;
fileReader.readAsText(blob);
});
}
}

The SAS token expiry bothers me.You cannot have a static SAS token that expires sooner unless we can set long expiry (user-delegation SAS token is short lived). Do we really have the capability to create the SAS token dynamically in javascript runtime? I think it's only possible in NodeJS runtime.

Related

Generate multiple docs and zip for download

I'm currently using docx.js in my react application I have setup with AWS Amplify (node backend). I am generating multiple documents and am saving them separately using the packer to generate the document as a blob and then use FileSaver.js's saveAs function to download. See code sample below:
const aDoc = new Document();
const bDoc = new Document();
const cDoc = new Document();
// Code that adds content to each doc
// Use packer to generate document as blob and download using FileSaver
Packer.toBlob(aDoc).then((blob) => {
// saveAs from FileSaver will download the file
FileSaver(blob, "aDoc.docx");
});
Packer.toBlob(bDoc).then((blob) => {
// saveAs from FileSaver will download the file
FileSaver(blob, "bDoc.docx");
});
Packer.toBlob(cDoc).then((blob) => {
// saveAs from FileSaver will download the file
FileSaver(blob, "cDoc.docx");
});
Now I'm wondering, how can I instead put these all into a ZIP file and have the user download that instead? Haven't really found much around, just this, which seems more like a workaround as it uses timeout to avoid issues when there are many documents--I'd rather avoid that and have it download in an archive instead. I've seen some libraries, like JSZip mentioned, but don't really understand how to get what docx.js is giving me into the archive.
Take a look at using JSZip - https://www.npmjs.com/package/jszip
I have reworked some code I used within a POC to how I believe it may work with your project and the code above.
var JSZip = require('jszip')
const Demo = () => {
const demoClick = () => {
var zip = new JSZip()
const zipFilename = 'test.zip'
const blobs = []
Packer.toBlob(aDoc).then((blob) => {
blobs.push(blob)
})
// repeat if needed
var urlArr = blobs // this will be your set of blobs you are downloading on their own right now.
urlArr.forEach(function (url) {
var filename = 'test.docx'
zip.file(filename, url, { binary: true })
})
zip.generateAsync({ type: 'blob' }).then(function (content) {
// you may need to work the content into a zip blob like this depending how FileSaver takes it
const zipContents = URL.createObjectURL(content)
//or
const zipContents = new Blob([content], {
type: 'application/zip'
})
// saveAs from FileSaver will download the file
FileSaver(content, zipFilename)
})
}
return <button onClick={demoClick}>demo</button>
}
If FileSaver doesn't like the format of the ZIP you could then use a more simple non imported download/save method
const zipContents = URL.createObjectURL(content)
if (window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveOrOpenBlob(content, zipFilename)
} else if (isIOS && isChrome) {
window.open(zipContents, '_blank')
} else {
const link = document.createElement('a')
link.href = zipContents
link.target = '_blank'
link.download = zipFilename
link.click()
}

Fail to upload local CSV file with fetch() and fail to execute FileReader()

I'm trying to manipulate a local CSV file with JavaScript. My purpose is to display datas from my CSV on my website, like we were using an external API in JSON format for example.
const csvLocalFile =
"http://XXX/.../file.csv";
const openFile = async () => {
const csv = await fetch(csvLocalFile).then();
let reader = new FileReader();
reader.onload = function () {
let text = reader.result;
filecontent = text.replace("", "");
};
reader.readAsText(csv.files[0]);
};
openFile();
Chrome display this error :
TypeError: Cannot read properties of undefined (reading '0')
When I delete "[0]" from "reader.readAsText(csv.files[0])", I have this message error :
TypeError: Failed to execute 'readAsText' on 'FileReader': parameter 1 is not of type 'Blob'.
A empty .then() isn't the problem, turns out it works fine without a fn. but you should remove it regardless.
The FileReader can't read Response objects, only File & Blobs...
You say that you try to read a local file, but yet you use fetch to get a remote file, so what is it that your really trying to do? it's unclear of how to help you...
a csv isn't valid json data, so you can't use .then((res) => res.JSON())
beside res.JSON() is wrong, it should be all lowercased... res.json()
The FileReader is considered legacy so you no longer need it... use await blob.text() instead
here are two examples of how to read 1 remote file using fetch
// Simulate getting a file from eg a file input or drag and drop
const file = new File(['id,name\n10,bob'], 'file.csv', { type: 'text/csv' })
// Simulate a remote location to get the csv from
const url = URL.createObjectURL(file)
const csvLocalFile = url // http://XXX/.../file.csv
const openFile = async () => {
const response = await fetch(csvLocalFile)
const text = await response.text()
console.log(text)
}
openFile()
...and another with a actual local file selected from a user input
const fileInput = document.querySelector('#fileInput')
fileInput.onchange = async () => {
const file = fileInput.files[0]
const text = await file.text()
console.log(text)
}
// ignore code below this line...
// Create a dummy file that we can use to change the file input with...
const dummyFile = new File(['id,name\n10,bob'], 'file.csv', { type: 'text/csv' })
// Used for creating a new FileList in a round-about way
const b = new ClipboardEvent('').clipboardData || new DataTransfer()
b.items.add(dummyFile)
// simulate picking a file
fileInput.files = b.files
fileInput.onchange()
<input type="file" id="fileInput">

Node-less way to generate a CID that matches IPFS-Desktop CID

I'd like to generate a CID (Content identifier) for a file in javascript without having access to an IPFS node or the internet. I've tried using js-multihashing-async to first hash the file and js-cid to generate a CID from the hash but I get a different CID than if I just add the file to ipfs-desktop. It looks like the problem is an IPFS node chunks data and the CID is for the DAG that links the files' chunks. I've tried this library but it doesn't produce the same CID as ipfs-desktop does for the same file. This question is essentially the same as mine but none of the answers give a CID that matches the ipfs-desktop-generated CID.
ipfs-only-hash is the right module to use to create an IPFS CID from a file or a Buffer, without needing to start an IPFS daemon. For the same input file and the same options, it should produce the same CID.
This example is from the ipfs-only-hash tests, where it verifies that it hashes the same buffer to the same CID as a js-ipfs node does.
test('should produce the same hash as IPFS', async t => {
const data = Buffer.from('TEST' + Date.now())
const ipfs = new Ipfs({ repo: path.join(os.tmpdir(), `${Date.now()}`) })
await new Promise((resolve, reject) => {
ipfs.on('ready', resolve).on('error', reject)
})
const files = await ipfs.add(data)
const hash = await Hash.of(data)
t.is(files[0].hash, hash)
})
https://github.com/alanshaw/ipfs-only-hash/blob/dbb72ccfff45ffca5fbea6a7b1704222f6aa4354/test.js#L21-L33
I'm one of the maintainers of IPFS Desktop, and under the hood, that app calls ipfs.add on http api for the local IPFS daemon here
When adding or hashing a file manually via the api, there are options to alter how files are chunked into blocks, how those blocks are linked together, and how the blocks are hashed. If any option values differ then the resulting hash and the CID that contains it will be different, even if the input file is the same.
You can experiment with those options and see a visualisation of the resulting DAG (Directed Acyclic Graph) structure here: https://dag.ipfs.io/
For a deep dive on how IPFS chunks and hashes files you can see the author of the ipfs-only-hash and maintainer of js-ipfs explain it here https://www.youtube.com/watch?v=Z5zNPwMDYGg
For the sake of posterity, here is how to match an image's CID downloaded via fetch to the CID generated from ipfs-desktop for the same image (added as a file from the local drive). You have to remove the prefix data:*/*;base64, that is prepended to the image's base64string and decode the string into a buffer array. Then you get the matching CID.
async testHashes() {
const url = "https://raw.githubusercontent.com/IanPhilips/jst-cids-test/master/src/23196210.jpg";
fetch(url)
.then(response => response.blob())
.then(blob => new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result);
reader.readAsDataURL(blob)
})).then(async dataUrl =>{
const strData = dataUrl as string;
// remove "data:*/*;base64," from dataUrl
const endOfPrefix = strData.indexOf(",");
const cleanStrData = strData.slice(endOfPrefix+1);
const data = Buffer.from(cleanStrData, "base64");
const hash = await Hash.of(data);
console.log("fetch data CID: " + hash); // QmYHzA8euDgUpNy3fh7JRwpPwt6jCgF35YTutYkyGGyr8f
});
console.log("ipfs-desktop CID: QmYHzA8euDgUpNy3fh7JRwpPwt6jCgF35YTutYkyGGyr8f");
}

create data url from fetched image

TL;DR
I'm trying to fetch and image, convert it to base64, and put the data url into an img's src attribute, but it's not working:
async function ajax(id) {
const tag = document.getElementById(id);
const path = tag.getAttribute("data-src");
const response = await fetch(path);
const blob = await response.blob();
const base64 = window.btoa(blob);
const content = `data:image/jpeg;base64,${base64}`;
tag.setAttribute("src", content);
}
The details, as well as some other methods, which do work follow.
I have been experimenting with different ways to lazy load:
$ mkdir lazy
$ cd lazy
$ wget https://upload.wikimedia.org/wikipedia/commons/7/7a/Lone_Ranger_and_Silver_1956.jpg # any other example image
now create a file called index.html with this in it:
<script>
// this works
function setAttribute(id) {
const tag = document.getElementById(id);
const path = tag.getAttribute("data-src");
tag.setAttribute("src", path);
}
// this doesn't work for some reason
async function ajax(id) {
const tag = document.getElementById(id);
const path = tag.getAttribute("data-src");
const response = await fetch(path);
const blob = await response.blob();
const base64 = window.btoa(blob);
const content = `data:image/jpeg;base64,${base64}`;
tag.setAttribute("src", content);
}
// this works too
async function works(id) {
const tag = document.getElementById(id);
const path = tag.getAttribute("data-src");
const response = await fetch(path);
const blob = await response.blob();
const content = URL.createObjectURL(blob);
tag.setAttribute("src", content);
}
</script>
set attribute<br />
data url<br />
object url<br />
<img id="example" data-src="Lone_Ranger_and_Silver_1956.jpg"></img><br />
and start a server in that folder:
$ python -m SimpleHTTPServer # or whichever local webserver
and then when I look at it in chrome I get this:
The first and third links both work:
However, the middle link does not:
Here is what the three links do to the tag respectively:
works:
<img id="example" data-src="Lone_Ranger_and_Silver_1956.jpg" src="Lone_Ranger_and_Silver_1956.jpg">
does not work:
<img id="example" data-src="Lone_Ranger_and_Silver_1956.jpg" src="data:image/jpeg;base64,W29iamVjdCBCbG9iXQ==">
works:
<img id="example" data-src="Lone_Ranger_and_Silver_1956.jpg" src="blob:http://localhost:8000/736a9e18-c30d-4e39-ac2e-b5246105c178">
That data url in the non working example also looks too short. So what am I doing wrong?
Thanks for the suggestion #dolpsdw. window.btoa doesn't do what I thought it would. If anybody is trying to do the same thing, instructions for reading a blob into a data url are here: https://stackoverflow.com/a/18650249/5203563
I have created this wrapper that fits right into my program as follows:
(it even adds in the data:image/jpeg;base64, part for you and works out the mime type from the blob)
function readBlob(b) {
return new Promise(function(resolve, reject) {
const reader = new FileReader();
reader.onloadend = function() {
resolve(reader.result);
};
// TODO: hook up reject to reader.onerror somehow and try it
reader.readAsDataURL(b);
});
}
async function ajax(id) {
const tag = document.getElementById(id);
const path = tag.getAttribute("data-src");
const response = await fetch(path);
const blob = await response.blob();
// const base64 = window.btoa(blob);
// const content = `data:image/jpeg;base64,${base64}`;
const content = await readBlob(blob);
tag.setAttribute("src", content);
}
this gives me the much longer data url that I expected:
When you have the inmemory blob
Just generate a url for that blob
var url = urlCreator.createObjectURL(blob)
Then create a new IMG with JavaScript and invoke decode method
const img = new Image();
img.src = url;
img.decode()
.then(() => {
document.body.appendChild(img);
})
.catch((encodingError) => {
// Do something with the error.
})
Maybe you want also to revoke URL after load with
URL.revokeObjectURL(objectURL)
About why the window.btoa does not work, its because is for string to base64 only.
Read about blob to base64 conversión here.
But is a more elegant solution createObjectURL.

NodeJS + ldapsj-client: problem saving thumbnailPhoto

Using the ldapsj-client module, I'm trying to save the thumbnailPhoto into a file
const auth = async () => {
const client = new LdapClient({ url: 'myaddomain' })
await client.bind('someemail#domain.com.br', 'passwaord')
const opts = {
filter: `(sAMAccountName=credential)`,
scope: "sub"
}
const s = await client.search(myBaseDN, opts)
console.log('thumbnailPhoto', s[0].thumbnailPhoto)
}
The console.log() outputs something like '����JFIF��C...'
I cannot figure out how to save this binary into a file. When I try several approaches, as explained here, does not work. It seems the data from AD is not in the same "format".
I tried to convert it into a Buffer and then, to base64
const buffer = Buffer.from(s[0].thumbnailPhoto, 'binary')
var src = "data:image/png;base64," + Buffer.from(s[0].thumbnailPhoto).toString('base64')
But the output is not a valid base64.

Categories

Resources