I have an async method that retrieves the presigned URL of a selected file and a method that takes that URL and downloads the file by adding an anchor tag, assigns the URL to href and then simulates a click event. It works fine for a single file download. However, when there are multiple files selected it doesn't download all the files. I have tried other approaches such as calling fetch(), axios etc but that also results in the same issue.
Below is my code:
method in component:
`public async downloadSelectedFiles(event: Event, entitlementId: string) {
event.preventDefault();
for (const releaseFile of this.selectedReleaseFiles) {
const url = await this._softwareProductsService.getFileDownloadUrl(entitlementId, String(releaseFile.id));
await this._downloadService.downloadFile(url);
}
}`
method in service:
`public async downloadFile(url: string): Promise<void> {
const a = this._document.createElement('a');
a.download = '';
a.href = url;
a.style.display = 'none';
document.body.appendChild(a);
a.click();
await this._delay(100);
document.body.removeChild(a);
}
private _delay(ms: number): Promise<void> {
return new Promise<void>((resolve) => {
setTimeout(resolve, ms);
});
}`
I have tried setting id of anchor tag. Waiting after click and after remove as well. Also tried 'multi-download' library. All result in same issue.
It doesn't download all the files when download button is clicked for the first time. When clicked again it works fine.
Is there a way to await on anchor tag click event?
Related
I am having issues with the code bellow. The function loadFile returns a promise that resolves to a file (blob) received from an input element. 90% of the time this code works as intended. A file open dialog appears and if I select an image file and click open the image is loaded and appended to the document body and the message "Resolving promise!" appears in the console. However, about 10% of the time I will select an image and click open, and then nothing will happen. No messages or errors appear in the console. The open file dialog simply closes and despite having selected an image the oninput handler never appears to fire as the promise never resolves. I suspect this may be a bug in Chrome.
function loadFile() {
return new Promise(resolve => {
const input = document.createElement("input")
input.type = "file"
input.oninput = function() {
console.log("Resolving promise!")
resolve(input.files[0])
}
input.click()
})
}
async function imageFromFile() {
const file = await loadFile()
const url = URL.createObjectURL(file)
const image = new Image()
image.src = url
await image.decode()
return image
}
imageFromFile().then(img => {
document.body.append(img)
})
You could wrap your async function in a try catch block (and log the error) to see what happens that 10% of times.
try {
await loadFile()
} catch(err) {
console.log(err)
}
In TestCafe, on click of an anchor tag, I have a redirect to a new page and an event that basically logs the click event to an object.
I'm wanting to test the value of the click event in the object in TestCafe, but because the redirect happens, I lose the object.
Manually I'm able to do this, as I can hold shift while clicking the link and open a new window for the redirected page, but keep the original page in the original window and then see the object in the console.
Trying to figure out if there's a way "simulate" a click, but not do the redirect. Or alternatively, be able to assert somehow immediately after the click, but before the redirect?
mini example:
await t
.click('a') // here it already redirects so I'll lose the object
.expect(object).contains('value')
The following test shows how you can disable and enable navigation for a link:
import { Selector, ClientFunction } from 'testcafe';
fixture `Navigation`
.page `example.com`;
const enableNavigationControl = ClientFunction(selector => {
const element = selector();
element.addEventListener('click', event => window.disableNavigation && event.preventDefault());
});
const disableNavigation = ClientFunction(() => window.disableNavigation = true);
const enableNavigation = ClientFunction(() => window.disableNavigation = false);
test('navigation', async t => {
const link = Selector('a');
await enableNavigationControl(link);
await disableNavigation();
await t.click(link);
// Perform assertions...
await enableNavigation();
await t.click(link);
});
I have a href link to download a template from my application. It is working fine in Chrome and IE but however it is not working in mobile devices( Android as well as iPhone)
I have this function which gets called by clicking on the link..
fileUrl: any;
getFileTemplate(): any {
this.productService.getFile().subscribe((response) => {
const fileContent = response;
// An application or a document that must be opened in an application
const blob = new Blob([fileContent], { type: 'application/octet-stream' });
if (window.navigator.msSaveBlob) {
// to download in IE
window.navigator.msSaveBlob(blob, 'abcd.csv');
} else {
this.fileUrl= this.sanitizer.bypassSecurityTrustResourceUrl(window.URL.createObjectURL(blob));
const a = document.createElement('a');
a.href = window.URL.createObjectURL(blob);
a.download = 'abcd.csv';
a.click();
}
});
}
And in HTML file
`<a href="javascript:void(null)"
(click)="getFileTemplate();"
id="link-inline-excel"
class="u-text--document u-text--document-link"
download="abcd.csv"><span>Title my file (7MB)</span></a>`
This is not working in mobile devices.
Am i missing something here?
You are more or less on the right track. The minimum modification I can offer to get your code to work is adding one more line to getFileTemplate:
getFileTemplate(): any {
this.productService.getFile().subscribe((response) => {
const fileContent = response;
// An application or a document that must be opened in an application
const blob = new Blob([fileContent], { type: 'application/octet-stream' });
if (window.navigator.msSaveBlob) {
// to download in IE
window.navigator.msSaveBlob(blob, 'abcd.csv');
} else {
this.fileUrl= this.sanitizer.bypassSecurityTrustResourceUrl(window.URL.createObjectURL(blob));
const a = document.createElement('a');
a.href = window.URL.createObjectURL(blob);
a.download = 'abcd.csv';
document.body.appendChild(a); //<-- Need to add the link to the DOM
a.click();
}
});
}
Of course, while this works, it's not a very clean solution. For one, the user will be able to see the newly appended link. For another, the Angular documentation suggests avoiding direct DOM manipulations, using Renderer2 instead.
Here's a StackBlitz example with both of these things in mind.
The example creates a separate Downloader component, that acts like a basic anchor element, but encapsulating the logic to trigger a download. You can then use this component wherever you want to trigger a file download.
The gist of the answer is this snippet:
...
constructor(
private element: ElementRef,
private renderer: Renderer2
) {}
...
download(data: Blob, filename: string) {
if(!data) {
return;
}
//Create the anchor element
const link: any = this.renderer.createElement('a');
//Create the URL
const url: any = URL.createObjectURL(data);
//Set the attributes for the anchor
this.renderer.setProperty(link, 'href', url);
this.renderer.setProperty(link, 'download', filename);
//Ensure that the anchor will be hidden, both visibly and from screen readers
this.renderer.setStyle(link, 'display', 'none');
//Add the anchor element to the DOM
this.renderer.appendChild(this.element.nativeElement, link);
//Trigger click on the anchor element to trigger the download
link.click();
//Cleanup by removing the element and revoking the URL.
this.renderer.removeChild(this.element.nativeElement, link);
URL.revokeObjectURL(url);
//Note: This is just a basic example, which does do DOM manipulation
//on every download. You could, instead, append the element in OnInit,
//adjusting its attributes when a download is triggered, and then
//remove the element in OnDestroy.
}
I have created a download manager and now I am creating its extensions. For firefox extension the problem is that I cannot find a decent way in which I can listen for creation of download and then stop the creation and redirect the url to my application. So here are two approaches I have already tried along with their drawbacks:
1. Using Downloads.jsm
const {Cu} = require("chrome");
Cu.import("resource://gre/modules/Downloads.jsm");
Cu.import("resource://gre/modules/Task.jsm");
let list;
let view = {
onDownloadAdded: download => {
Task.spawn(function () {
try {
// Send download.source.url to my application
yield download.finalize(true);
yield list.remove(download);
} catch (ex) {
console.error(ex);
}
});
}
};
Task.spawn(function () {
list = yield Downloads.getList(Downloads.ALL);
yield list.addView(view);
}).then(null, Cu.reportError);
exports.onUnload = function (reason) {
list.removeView(view);
};
Problem: The problem with this approach is that user have to 1st click save as dialog then click start button on my download manager which is totally unacceptable. If I have to use this approach I need some way to remove this save as dialog. And because I am canceling download when its added the download is shown as canceled in "Show all downloads" dialog in firefox.
2. Using http-on-modify-request
var dlExtensions = [".exe", ".iso"];
const {components, Cc, Ci} = require("chrome");
httpRequestObserver =
{
observe: function (aSubject, aTopic, aData) {
if (aTopic == "http-on-modify-request") {
let url;
aSubject.QueryInterface(Ci.nsIHttpChannel);
url = aSubject.URI.spec;
for (var x in dlExtensions) {
if (url.endsWith(dlExtensions[x])) {
aSubject.cancel(components.results.NS_BINDING_ABORTED);
// Send url to my application
break;
}
}
}
}
};
var observerService = components.classes["#mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
observerService.addObserver(httpRequestObserver, "http-on-modify-request", false);
Problem: This code works as expected but the problem is I have to define all download Extensions of file to download and there are too many of them.
Requirement: So basically I need a listener that listens for creation of download in firefox and a way in which I can stop regular download dialog appearing and make my code execute instead.(Note that the listener present in method 1 is not useable because it listens for addition of download in download list not for the creation of download that leads to creation of download dialog)
I want to pass the input file from content page to extension background script, and then load it with FileReader() in the extension background script.
So in the web page I have a <input type="file"> and from onchange event I pass the file from content script to background page like this:
var myfile = document.getElementById('fileid').files[0];
chrome.runtime.sendMessage({myevent: "start", inputfile: myfile}, function(response) {});
in the background script I have this:
chrome.runtime.onMessage.addListener(function(message,sender,sendResponse){
if(message.myevent==="start")
{
var reader = new FileReader();
reader.onload = function(e) {
// file is loaded
}
reader.readAsArrayBuffer(message.inputfile);
}
});
but FileReader not load it, I'm not sure if this is correct way , but all i need is to pass the input file element to background script and load it with FileReader to send it with HTTP POST from background script. Please tell me what is wrong or how to do it correctly. It will help a lot if I see a sample code, because I'm new to chrome extension development, and not so experienced.
All messages send through the Chrome extension messaging API MUST be JSON-serializable.
If you want to get the contents of a file at the background page, you'd better create a (temporary) URL for the File object, pass this URL to the background page and use XMLHttpRequest to grab its contents:
// Create URL
var url = URL.createObjectURL(myfile);
// Pass URL to background page (ommited for brevity) and load it..
var x = new XMLHttpRequest();
x.onload = function() {
var result = x.response;
// TODO: Use [object ArrayBuffer]
};
x.open('GET', url); // <-- blob:-url created in content script
x.responseType = 'arraybuffer';
x.send();
Though why do you want to send the file to the background page? Content scripts can also send cross-origin requests.
This works for chrome. You could find the whole production code here.
https://github.com/Leslie-Wong-H/BoostPic/tree/7513b3b8d67fc6f57718dc8b9ff1d5646ad03c75/BoostPic_Chrome/js
main.js:
// Crossbrowser support for URL
const URLObj = window.URL || webkitURL;
// Creates a DOMString containing a URL representing the object given in the parameter
// namely the original Blob
const blobUrl = URLObj.createObjectURL(imageBlob);
console.log(blobUrl);
chrome.runtime.sendMessage(blobUrl, (res) => {
imgUrl = res;
console.log(imgUrl);
clearInterval(refreshIntervalId);
// To prevent that it happens to halt at " Image uploading ..."
setTimeout(() => {
var imgUrlText = document.querySelector(imgUrlTextBoxId);
imgUrlText.value = imgUrl;
}, 1000);
// double check to clear interval to prevent infinite error loop of LoadingStateOne
// Hope it works.
setTimeout(() => {
clearInterval(refreshIntervalId);
}, 500);
console.log("Stop uploading state message");
background.js:
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.startsWith("blob")) {
console.log("RECEIVED");
getBase64Url(request).then((res) => {
console.log("Arrived here");
// Acquired from https://stackoverflow.com/questions/18650168/convert-blob-to-base64/18650249#
const reader = new FileReader();
reader.readAsDataURL(res);
reader.onloadend = function () {
const base64data = reader.result;
console.log(base64data);