Hi,
I know I can use downloads.download() to provide users with a way to “export” addon settings in the StorageArea to their local filesystem. But I’m unable to find any examples or see how to do this.
Any suggestions?
thanks,
eric
Hi,
I know I can use downloads.download() to provide users with a way to “export” addon settings in the StorageArea to their local filesystem. But I’m unable to find any examples or see how to do this.
Any suggestions?
thanks,
eric
Hey Eric,
I copied a piece of code i use in VDH: https://pastebin.com/90BJWHsY
In my case, i create an empty file because i am only interested in getting the full path and reserving the unique file name, but you can change the line:
var file = new File(new Array(),fileName);
to put some actual content in your file.
Note that this code is a bit more complicated than what you need if you only want to support Firefox. Chrome does things differently.
Hope this helps.
/mig
EDIT: this just addresses exporting the data to a file, not getting data from the storage area which should be simpler.
Mig, thanks! Hope you are well.
Of course I would like to support Chrome, too, so thank you for the complete example. The call to browser.downloads.search() is strange. Why do that?
I call browser.downloads.search
because browser.downloads.download
returns, through a Promise, a downloadItemId
, but what i’m really interested in is the field filename
of the downloadItem which contains the full path to the chosen file.
It is interesting to note that on Firefox, browser.downloads.download
resolves after the user has chosen the file (assuming field saveas
is true
), so you need to call browser.downloads.search
to get the details. On Chrome, browser.downloads.download
resolves immediately, and you get a notification with downloadItem details through the listener when the file name is chosen by the user.
You should also keep in mind that on Firefox, if the user cancels the SaveAs dialog, browser.downloads.download
rejects the promise, while in Chrome, you receive an event with an error.
Those differences make the code more complicated than what it should be. If you are only interested in resolving the promise once the file has been written, you can simplify the whole thing by just installing a downloads.onChanged
listener and wait for a change where your downloadItem state is “complete” (details.state && details.state.current === "complete"
)
Something like this should do it (both Chrome & Firefox):
function SaveFile(fileName, content) {
return new Promise((resolve, reject) => {
var file = new File(new Array(content), fileName);
browser.downloads.download({
url: URL.createObjectURL(file),
conflictAction: "uniquify",
filename: fileName,
saveAs: true,
})
.then((downloadId) => {
function Listener(details) {
if (details.id == downloadId)
if (details.error)
resolve(false);
else if (details.state) {
if (details.state.current != "in_progress") {
browser.downloads.onChanged.removeListener(Listener);
resolve(details.state.current == "complete");
}
}
}
browser.downloads.onChanged.addListener(Listener);
})
.catch((err) => {
resolve(false);
});
});
}
SaveFile("test.txt", "Toto a faim")
.then((result) => {
console.info("SaveFile ok", result);
})
.catch((err) => {
console.info("SaveFile ko", err);
});
Check one of my addons with Export feature (e.g Edit, FoxyImage, Historia, etc)
I have a modular code that I copy/paste into all of them.
Thank you! I get this error on the Blob constructor:
TypeError: Argument 1 of Blob.constructor can’t be converted to a sequence
Here is my code:
getAllSettings().then((settings) => {
let blob = new Blob(JSON.stringify([settings], null, 2), {type : 'text/json'});
let filename = "some-settings.json";
chrome.downloads.download({
url: URL.createObjectURL(blob),
filename,
saveAs: true,
conflictAction: 'uniquify'
});
});
Here is the output of JSON.stringify([settings], null, 2).
I’ve used database blobs before but not webAPI blobs before. Any ideas what I’m doing wrong? I’m also slightly confused at the syntax for filename. I thought you’d need a keyName/colon prefix like for url, saveAs, and conflictAction.
Thanks,
Eric
I believe the first argument of new Blob(...)
should be an array, so you should try:
let blob = new Blob([JSON.stringify([settings], null, 2)], {type : 'text/json'});
Indeed, you are right. I made the 1st arg to JSON.stringify an array instead. But now I get a different error. chrome.downloads
is not defined; yeah, i know that. Ok, so I change chrome
to browser
but then get browser.downloads
is undefined. This is Firefox 58 (nightly). I know browser
is defined in this module because i’m using it elsewhere in the same module. It’s an options module. What gives?
You should use the webtension-polyfill library, then you can use browser
everywhere. All Chrome API calls like chrome.api.fnt(...args,callback)
should then be written browser.api.fnt(...args)
with the function returning a Promise instead of invoking the callback.
OK, but I dont think this is related to polyfill use. I’m in Firefox and using the browser object, not Google chrome or the chrome object. Right now, this is strictly a Firefox-only addon.
Sorry, i misread your issue.
Your problem may be that you did not include downloads
in the manifest.json
permissions.
The error is here. You are passing a string while the blob wants array.
let blob = new Blob(JSON.stringify([settings], null, 2),.......
Try this:
getAllSettings().then((settings) => {
let data = JSON.stringify(settings, null, 2);
let blob = new Blob([data], {type : 'text/json'});
let filename = 'some-settings.json';
chrome.downloads.download({
url: URL.createObjectURL(blob),
filename,
saveAs: true,
conflictAction: 'uniquify'
});
});
Or if you prefer to combine them:
getAllSettings().then((settings) => {
let blob = new Blob([JSON.stringify(settings, null, 2)], {type : 'text/json'});
let filename = 'some-settings.json';
chrome.downloads.download({
url: URL.createObjectURL(blob),
filename,
saveAs: true,
conflictAction: 'uniquify'
});
});
I also had to find this by trial and error at the time.
Have you added “downloads” to permissions?
Where are you running the code? The API is not available in content scripts.
Yes! Thank you so much.