How to trigger native browser video download using JS - javascript

i want to write a button where user clicked will automatically download a video from a URL.
I have researched on someway, here is my implementation
const startDownload = (
url: string | undefined,
filename = 'Tiktok_livestream.mp4',
) => {
if (!download?.visible || clickedDownloadRef.current || !url) {
console.log('here', download, clickedDownloadRef.current, url);
return;
}
clickedDownloadRef.current = true;
fetch(url)
.then(response => response.blob())
.then(blob => {
console.log('finish downloading blob');
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = filename;
link.click();
})
.catch(console.error);
console.log('download');
};
So the idea is we use fetch to download the data in to blob. Then save it.
The problem of this method is it doesnt have good UX for big files. Because the user only see the downloaded file widget of chrome after the fetch is done ( which take long time for big files). compared to a native video downloading where it will show when user start the download, and user can also see the progress.
So i wonder is there any otherway that will trigger the chrome widget right after user click the button, and continously show the progress
The widget im talking about is the native one usually automatically showed when chrome is downloading something

In order to see the download progress natively, the server you are downloading from has to send the Content-Length header in its HTTP response. If you are downloading from a server that doesn't send this header, then your only recourse would be to put up your own relay server that your users send the file request to, and it responds by piping the video file from the true source and attaching the Content-Length header to the HTTP response it sends back to the user.

Related

Instantly stream download a file in the browser from an external API that requires custom HTTP request headers

An external API requires authentication on the request via the custom header SessionToken.
One of the endpoints allows downloading a file. This works as shown with the simplified snippet below.
The implemented solution is less than ideal: The file is first downloaded in the background without any user feedback. Only when it is completely downloaded - and the Promise resolves - an actual file download is triggered in the browser.
This is mainly an issue for very large files that take some time to download: The user does not have any feedback and only sees the actual file download when it is already downloaded.
Current situation: User only sees file download in the browser after the file has already been completely downloaded in the background.
Desired situation: The file download should be triggered immediately (file download instantly visible in the "Downloads" section of the browser)
There is no control over the external API. Currently using the axios HTTP client is used but using any other library would be OK if it solves the issue.
Snippet:
import axios from 'axios';
/**
* The external API does not send the Content-Length response header,
* so no download progress is known until completely downloaded.
* No real progress can be shown to the user on the page,
* apart from showing a spinner or indeterminate progress bar
*
* #param progressEvent
*/
const onDownloadProgress = (progressEvent) => {
const {
loaded, total, progress, bytes, estimated, rate, download = true,
} = progressEvent;
console.log('completed: ', progress); // undefined, see docblock
}
const downloadFile = async ({
url,
filename,
sessionToken,
}) => {
const headers = {
Accept: 'application/octet-stream',
SessionToken: sessionToken,
};
return new Promise((resolve, reject) => {
axios.get(url, {
responseType: 'blob',
headers,
onDownloadProgress,
}).then((response) => {
// trigger a browser file download by creating a new anchor tag that points to the downloaded data, and clicking it
const href = URL.createObjectURL(response.data);
const link = document.createElement('a');
link.href = href;
link.setAttribute('download', filename);
document.body.appendChild(link);
link.click();
// cleanup
document.body.removeChild(link);
URL.revokeObjectURL(href);
resolve({ filename, response });
}).catch((error) => {
reject(error);
});
});
};
export default downloadFile

Download PDF file form embed tag using Puppeteer

I am trying to download a pdf from a Website.
The website is made with the framework ZK, and it reveals a dynamic URL to the PDF for a window of time when an id number type in a input bar. This step is easy enough and I a able to get the PDF URL which opens up in the browser on a embedded tag.
However, it has been impossible for me to find a way to download the file to my computer. For days, I have tried and read everything from this, to this, to this.
The closes thing I have been able to get with this code:
let [ iframe ] = await page.$x('//iframe');
let pdf_url = await page.evaluate( iframe => iframe.src, iframe)
let res = await page.evaluate( async url =>
await fetch(url, {
method: 'GET',
credentials: 'same-origin', // usefull when we are logged into a website and want to send cookies
responseType: 'arraybuffer', // get response as an ArrayBuffer
}).then(response => response.text()),
pdf_url
)
console.log('res:', res);
//const response = await page.goto(pdf);
fs.writeFileSync('somepdf.pdf', res);
This results in a blank PDF file which is of 92K in size.
While the file I am trying to get is of 52K. I suspect the back-end might be sending me 'dummy' pdf file because my headers on the fetch request might not be correct.
What else can I try?
Here is the link to the PDF page.
You can use the random ID number I found: '1705120630'

how to download file using api angular

I have an API that downloads a file, I have a button on the button I have a click that sends a request to the API for download a file, but it doesn't work request sending successfully but the file is not downloaded, but when I'm adding the URL into the browser the file is successfully downloaded
HTML
<button (click)="exportFile()">Download</button>
TS
exportFile(): void{
this.companiesService.export().subscribe((res) => {
console.log(res);
});
}
Service
export(){
const headers = this.httpOptions.headers.set('Authorization', `Bearer ${this.cookieService.get('access-token')}`);
return this.http.get(`${this.API_URL}/company/export/`,{headers});
}
You need to process the returned blob and save it as a file. Just returning it is not enough. Perhaps this demo can give you more insight how to improve your service. https://stackblitz.com/edit/angular-file-download-progress-qsqsnf?file=src%2Fapp%2Fdownload.ts

Electron upload with progress

I have an Electron app which is able to upload very big files to the server via HTTP in renderer process without user input. I decided to use axios as my HTTP client and it was able to retrieve upload progress but with this I met few problems.
Browser's supported js and Node.js aren't "friendly" with each other in some moments. I used fs.createReadStream function to get the file but axios does not understand what ReadStream object is and I can't pipe (there are several topics on their GitHub issue tab but nothing was done with that till now) this stream to FormData (which I should place my file in).
I ended up using fs.readFileSync and then form-data module with its getBuffer() method but now my file is loaded entirely in the memory before upload and with how big my files are it kills Electron process.
Googling I found out about request library which in-fact is able to pipe a stream to request but it's deprecated, not supported anymore and apparently I can't get upload progress from it.
I'm running out of options. How do you upload files with Electron without user input (so without file input) not loading them in the memory upfront?
P.S. on form-data github page there is a piece of code explaining how to upload a file stream with axios but it doesn't work, nothing is sent and downgrading the library as one issue topic suggested didn't help either...
const form = new FormData();
const stream = fs.createReadStream(PATH_TO_FILE);
form.append('image', stream);
// In Node.js environment you need to set boundary in the header field 'Content-Type' by calling method `getHeaders`
const formHeaders = form.getHeaders();
axios.post('http://example.com', form, {
headers: {
...formHeaders,
},
})
.then(response => response)
.catch(error => error)
I was able to solve this and I hope it will help anyone facing the same problem.
Since request is deprecated I looked up for alternatives and found got.js for NodeJS HTTP requests. It has support of Stream, fs.ReadStream etc.
You will need form-data as well, it allows to put streams inside FormData and assign it to a key.
The following code solved my question:
import fs from 'fs'
import got from 'got'
import FormData from 'form-data'
const stream = fs.createReadStream('some_path')
// NOT native form data
const formData = new FormData()
formData.append('file', stream, 'filename');
try {
const res = await got.post('https://my_link.com/upload', {
body: formData,
headers: {
...formData.getHeaders() // sets the boundary and Content-Type header
}
}).on('uploadProgress', progress => {
// here we get our upload progress, progress.percent is a float number from 0 to 1
console.log(Math.round(progress.percent * 100))
});
if (res.statusCode === 200) {
// upload success
} else {
// error handler
}
} catch (e) {
console.log(e);
}
Works perfectly in Electron renderer process!

Sending a file to the client from Node.js with Express

I have a unique situation in terms of difficulty.
I need to send HTML to the server, have the server convert the HTML to a PDF, send that PDF back to the client, and then download the PDF using client-side code.
I have to do it this way because I'm using client-side routing, so the only way I can access my endpoint that should perform this action is via a GET Request with Ajax or Fetch from client-side JavaScript. I am aware of res.sendFile(), but that attempts to render the file in the browser - I don't want that - rather, I want to be able to use client-side code to download the file.
Is it possible, then, to send a PDF file from temporary storage on the server down to the client, allowing client-side code to do whatever it wants to the file thereafter - in my case, downloading it?
I don't believe I have to provide any code because this is more of a theoretical question.
My issue stemmed from the fact that I could not just use res.sendFile() or res.download() from Express because the route was not being accessed by the browser URL bar, rather, my application uses client-side routing, and thus I had to make an HTTP GET Request via Fetch or XMLHttpRequest.
The second issue is that I needed to build the PDF file on the server based on an HTML string sent from the client - so again, I need to make a GET Request sending along a request body.
My solution, then, using Fetch, was to make the Get Request from the client:
fetch('/route' , {
method: 'GET',
body: 'My HTML String'
});
On the server, I have my code that converts the HTML string to a PDF, using the HTML-PDF Node module, and then, I convert that file to a Base64 String, setting the MIME Type and appending data:application/pdf;base64,.
app.get('/route', (req, res) => {
// Use req.body to build and save PDF to temp storage (os.tempdir())
// ...
fs.readFile('./myPDF.pdf', (err, data) => {
if (err) res.status(500).send(err);
res.contentType('application/pdf')
.send(`data:application/pdf;base64,${new Buffer.from(data).toString('base64')}`);
});
});
Back on the client, I have my aforementioned Fetch Request, meaning I just need to tack on the promise to get the response:
fetch('/route', {
method: 'POST',
body: 'My HTML String' // Would define object and stringify.
})
.then(res => res.text())
.then(base64String => {
// Now I just need to download the base64String as a PDF.
});
To make the download, I dynamically create an anchor tag, set its href attribute to the Base64 String in the response from the server, give it a title, and then programmatically click it:
const anchorTag = document.createElement('a');
anchorTag.href = base64String;
anchorTag.download = "My PDF File.pdf";
anchorTag.click();
So, all together and on the client:
fetch('/route', {
method: 'POST',
body: 'My HTML String' // Would define object and stringify.
})
.then(res => res.text())
.then(base64String => {
const anchorTag = document.createElement('a');
anchorTag.href = base64String;
anchorTag.download = "My PDF File.pdf";
anchorTag.click();
});
The solution for using an anchor tag to trigger the download came from another StackOverflow answer. It's also important to note that Base64 Encoding is not very efficient. Better solutions exist, but for my purposes, Base64 will work fine.
It is also imperative to note that Base64 Encoding is precisely that - an Encoding Scheme, not, I repeat, not an Encryption Scheme. So if your PDF files contain privileged information, you would likely want to add token authentication to the endpoint and encrypt the file.

Categories

Resources