Save a file without the save dialog box in Chrome Apps - javascript

In my chrome app, I am writing some data to a file.
I used the following code available in the official website under Storage APIs
chrome.fileSystem.chooseEntry({type: 'saveFile'}, function(writableFileEntry) {
writableFileEntry.createWriter(function(writer) {
writer.onerror = errorHandler;
writer.onwriteend = function(e) {
console.log('write complete');
};
writer.write(new Blob(['1234567890'], {type: 'text/plain'}));
}, errorHandler);
});
Now I need to avoid popping up the save dialog box, and save this file in a predefined location.
Is this possible to do?

If you want persistent data but don't want to bother the user with the location of the data on the local machine, you have several options:
Local storage
IndexedDB
HTML5 file API
chrome.storage
chrome.storage.sync
chrome.syncFileSystem
None of these will give you real files that the user can access. If the user (in addition to your own app) needs to be able to access the file that you've written, then the user needs to be asked where to put the file. As Cerbrus said in the comment to your question, silently putting a file somewhere on the user's machine would be a security issue.
A new feature in Chrome 31 is chrome.fileSystem.retainEntry. Using this API, your app will keep the ability to retain a number of fileEntries, including directories, across app restarts, so that you won't have to keep bugging the user to choose the file location. It's my understanding that there are restrictions in which locations the user is allowed to pick, so that a malicious app can't direct the user to instruct Chrome to overwrite system files.

Related

How to write file in Google Chrome App without prompting?

I am fumbling around with the free Chrome Dev Editor on my Chromebook. I am trying to use the fileSystem to read and write .txt files. It is all very wrapped up, not at all like in C. I can no more tell if I am even allowed to do something, let alone where the proper place is to find out how.
I think the files I can see using the Files thingy are in the sandbox that I am allowed to play in (meaning, folders that are accessible by the app?) The root is called Downloads. Sure enough, if I use all the dot calls and callback arguments for the read, as in the examples at developer.chrome.com/apps/filesystem, it works. But I have to have a prompt
every time for both reads and writes.
A little more Googling came up with this trick: (I think it was here in stackoverflow, in fact) a chrome.runtime call, getPackagedDirectoryEntry, that seems to give me a handle to the folder of my app. Great! That's all I need to not have to go through the prompting. For the readfile, anyway.
But then trying to apply the same trick to the writefile did not work. In fact, it did nothing discernible. No errors, no complaints. Nothing. Even though the write file with prompting works fine (so presumably I have the permissions and Blob construction right.) What to do?
Here is my code:
function test(){
// Samsung 303C Chromebook - Chrome Dev Editor - /Downloads/Daily/main.js
// prompted write
chrome.fileSystem.chooseEntry({type:'saveFile'},function(a){
a.createWriter(function(b){
b.write(new Blob(["Programming fun"],{type:'text/plain'}));
},function(e){trace.innerText = 'error is ' + e;});
});
// unprompted read
chrome.runtime.getPackageDirectoryEntry(function(a){
a.getFile('text.txt',{},function(b){
b.file(function(c){
var d = new FileReader();
d.onloadend = function(){trace.innerText = this.result;};
d.readAsText(c);
});
});
});
// unprompted write - why not?
chrome.runtime.getPackageDirectoryEntry(function(a){
a.getFile('new.txt',{create:true},function(b){
b.createWriter(function(c){
c.write(new Blob(["Miss Manners fan"],{type:'text/plain'}));
},function(e){trace.innerText = 'error is ' + e;});
});
});
}
To be fair, Filesystem API is a big mess of callbacks and it's not unreasonable to get drowned in it.
It's not currently documented, but chrome.runtime.getPackageDirectoryEntry returns a read-only DirectoryEntry, and there is no way to make it writable (it's specifically blacklisted).
You probably don't see an error, because it fails at the getFile stage, for which you don't have an error handler.
Unfortunately, for a Chrome App the only option to write out to a real filesystem is to prompt the user. However, you can retain the entry and ask only once.
If you don't need to write out to the real filesystem but need only internal storage, HTML Filesystem API can help you (yes, it's marked as abandoned, but Chrome maintains it since chrome.fileSystem is built on it).
Extensions additionally have access to chrome.downloads API that enables writing to (but not reading) the Downloads folder.
P.S. What you see in Files app is your "real" local filesystem in ChromeOS + mounted cloud filesystems (e.g. Google Drive)
You can use the basic web Filesystem API. First, add the "unlimitedStorage" permission. Then, copy the packaged files to the sandboxed filesystem, like this:
chrome.runtime.getPackageDirectoryEntry(function(package) {
package.getMetadata(function(metadata) {
webkitRequestFileSystem(PERSISTENT, metadata.size, function(filesystem) {
package.copyTo(filesystem.root)
})
})
})

Saving files in a Chrome App

Summary
Normally I could download a bunch of files, but Chrome Apps won't show the download shelf when a download occurs. What would be the best way of getting around this limitation of Chrome Apps?
Ideas
I could go about this by creating a zip file, but this would require the user to perform an extra step of unzipping the file.
I'm able to silently download the files, and so I could display a prompt to the user when the file is downloaded, but this would require the user to manually search for the file in their downloads folder.
What I've Learned
Everywhere on the internet tells me to use Chrome's download API, but this only works for Chrome extensions and not Chrome apps.
I can't bring up a save as window because 50 save as windows for 50 files is unacceptable
I can, however, bring up a prompt using chrome.fileSystem.chooseEntry({'type': "openDirectory"} to ask the user to choose a directory, but I can't find a way of saving to that directory.
My question is basically the same as How can a Chrome extension save many files to a user-specified directory? but for a Chrome app instead of an extension.
Project and Example Code
The app I'm building will be the same as this webpage I've built, but with a few modifications to make it work as a web-app.
This is how my website solves the problem
let example_pic = "data:image/png;base64,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"
let a = document.createElement("a");
a.href = example_pic;
document.body.appendChild(a)
a.click();
window.URL.revokeObjectURL(a.href);
a.remove()
I can, however, bring up a prompt using chrome.fileSystem.chooseEntry({'type': "openDirectory"}) to ask the user to choose a directory, but I can't find a way of saving to that directory.
That's what you need to work on.
Suppose you declare all the sub-permissions for the fileSystem API:
"permissions": [
{"fileSystem": ["write", "retainEntries", "directory"]}
]
Then you can:
Get an entry from the user:
chrome.fileSystem.chooseEntry({'type': "openDirectory"}, function(dirEntry) {
// Check for chrome.runtime.lastError, then use dirEntry
});
Retain it, so you can reuse it later without asking the user again:
dirEntryId = chrome.fileSystem.retainEntry(dirEntry);
// Use chrome.storage to save/retrieve it
chrome.fileSystem.restoreEntry(dirEntryId, function(entry) { /* ... */ });
Using the HTML FileSystem API, create files in the directory:
dirEntry.getFile(
"test.txt",
{create: true}, // add "exclusive: true" to prevent overwrite
function(fileEntry) { /* write here */ },
function(e) { console.error(e) }
);

Implement cross extension message passing in chrome extension and app

I am trying to do cross extension message passing between chrome extension and chrome app according to this article. But I am not sure that how to do it correctly. I used background js to receive and send messages. But no clue whether it is working or not. Actually I want to save file from chrome extension, since it cannot be done I thought this could work. So any idea or suggestion or example is highly welcome.
I have go through many alternatives as also appears in this question. Then one of answer points this example. I found that this example is works fine. I hope that I could use this mechanism to save file using Chrome App's fileSystem API.
The Chrome messaging APIs can only transfer JSON-serializable values. If the files are small, then you could just read the file content using FileReader in the extension, send the message over the external messaging channel to the Chrome App, then save the data using the FileWriter API.
When the files are big, read the file in chunks using file.slice(start, end) then follow the same method as for small files.
Extension:
var app_id = '.... ID of app (32 lowercase a-p characters) ....';
var file = ...; // File or Blob object, e.g. from an <input type=file>
var fr = new FileReader();
fr.onload = function() {
var message = {
blob: fr.result,
filename: file.name,
filetype: file.type
};
chrome.runtime.sendMessage(app_id, message, function(result) {
if (chrome.runtime.lastError) {
// Handle error, e.g. app not installed
console.warn('Error: ' + chrome.runtime.lastError.message);
} else {
// Handle success
console.log('Reply from app: ', result);
}
});
};
fr.onerror = function() { /* handle error */ };
// file or sliced file.
fr.readAsText(file);
App:
chrome.runtime.onMessageExternal.addListener(
function(message, sender, sendResponse) {
// TODO: Validate that sender.id is allowed to invoke the app!
// Do something, e.g. convert back to Blob and do whatever you want.
var blob = new Blob([message.blob], {type: message.filetype});
console.log('TODO: Do something with ' + message.filename + ':', blob);
// Do something, e.g. reply to message
sendResponse('Processed file');
// if you want to send a reply asynchronously, uncomment the next line.
// return true;
});
EDIT: Although the following method using sounded nice in theory, it does not work in practice because a separate SharedWorker process is created for the app / extension.
If you want to send huge files (e.g. Files), then you could implement the following:
Extension: Create proxy.html (content = <script src=proxy.js></script>). (feel free to pick any other name).
Extension: Put proxy.html in web_accessible_resources.
App: Bind a window.onmessage event listener. This event listener will receive messages from the frame you're going to load in the next step.
App: Load chrome-extension://[EXTENSIONID]/proxy.html in a frame within your app. This extension ID can either be hard-coded (see Obtaining Chrome Extension ID for development) or exchanged via the external extension message passing API (make sure that you validate the source - hardcoding the ID would be the best way).
Extension: When proxy.html is loaded, check whether location.ancestorOrigins[0] == 'chrome-extension://[APPID]' to avoid a security leak. Terminate all steps if this condition fails.
Extension: When you want to pass a File or Blob to the app, use parent.postMessage(blob, 'chrome-extension://[APPID]');
App: When it receives the blob from the extension frame, save it to the FileSystem that you obtained through the chrome.fileSystem API.
The last task to solve is getting a file from the extension to the extension frame (proxy.html) that is embedded in the app. This can be done via a SharedWorker, see this answer for an example (you can skip the part that creates a new frame because the extension frame is already created in one of the previous steps).
Note that at the moment (Chrome 35), Files can only be sent with a work-around due to a bug.

Saving Files Locally

I'm trying to create a page where there are links to online pdf's.
When you click these links, it will save the file locally, and add a name / path to local storage.
I then iterate over the local storage keys, to display links to each saved file.
I'm having issues with saving files locally. I tried using chrome filesystem api:
function saveFile() {
chrome.fileSystem.chooseEntry({
type: "saveFile",
suggestedName: "file.txt"
},
function (savedFile) {
localStorage[s] = saveFile.fullPath;
});
}
but I get Uncaught TypeError: Cannot read property 'chooseEntry' of undefined.
Essentially, I need to save a file to the system, and get that path. It is preferable if there is no prompt to select name/location.
If the app is a Chrome extension then I think it is likely that the fileSystem API has not been enabled. For a chrome app you need to enable it via the manifest.json file for that application, you may also be able to ask for user permission.
If it is for a web application then you can request the file system using window.webkitRequestFileSystem although this will only be possible in Chrome (and maybe still Opera).
For cross browser file storage and download support you could use something like Dexie for browser IndexedDB storage and the FileSaver and Blob libraries although you will not have the same storage capacity and flexibility than with the native chrome APIs. Here is an example for FileSaver & Blob:
var blob = new Blob([data.text], {
type: "text/csv;charset=utf-8"
});
saveAs(blob, data.label + ".csv");
Is this a Google Chrome Application?
If so, you must have the following entry in your manifest file:
"permissions" : [
{
"fileSystem" : ["write", "directory"]
}
]
(Be careful with the spelling. I encountered the error because I wrote permission instead of permissions)

There is any way to save image/pdf content into local file system applicable for all browsers [closed]

It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 10 years ago.
I got image content by ajax response in an array buffer.appended that array buffer to blob builder.now i want to write these contents to a file.Is there any way to do this..?
I used windows.requestFileSystem it is working fine with chrome but in mozilla not working..
here is my piece of code ,
function retrieveImage(studyUID, seriesUID, instanceUID, sopClassUID,nodeRef) {
window.requestFileSystem = window.requestFileSystem||window.webkitRequestFileSystem;
var xhr = new XMLHttpRequest();
var url="/alfresco/createthumbnail?ticket="+ticket+"&node="+nodeRef;
xhr.open('GET', url, true);
xhr.responseType = 'arraybuffer';
xhr.onload = function(e) {
if(this.status == 200) {
window.requestFileSystem(window.TEMPORARY, 1024*1024, function(fs) {
var fn = '';
if(sopClassUID == '1.2.840.10008.5.1.4.1.1.104.1') {
fn = instanceUID+'.pdf';
} else {
fn = instanceUID+'.jpg';
}
fs.root.getFile(fn, {create:true}, function(fileEntry) {
fileEntry.createWriter(function(writer) {
writer.onwriteend = function(e) {
console.log(fileEntry.fullPath + " created");
}
writer.onerror = function(e) {
console.log(e.toString());
}
var bb;
if(window.BlobBuilder) {
bb = new BlobBuilder();
} else if(window.WebKitBlobBuilder) {
bb = new WebKitBlobBuilder();
}
bb.append(xhr.response);
if(sopClassUID == '1.2.840.10008.5.1.4.1.1.104.1') {
writer.write(bb.getBlob('application/pdf'));
} else {
writer.write(bb.getBlob('image/jpeg'));
}
}, fileErrorHandler);
}, fileErrorHandler);
}, fileErrorHandler);
}
};
xhr.send();
}
The script of a web page is not allowed to write arbitrary files [such as pdfs] to client's storage. And you should be thankful because that means that web pages have a hard time trying to put malware on your machine.
Instead you should redirect the user (or open a new window/tab) to an url where the browser can find the content desired for download, and let it handle it. Use the header to tell the client to download it or displayed as explained here.
If you need to create the downloaded content dynamically, then manage it on the server making it an active page (.php, .jsp, .aspx, etc...). What matters is to have the correct MIME type in the header of the response.
Note: yes, I'm telling you to not use ajax, just window.open. Edit: I guess you may want to present the images in a img, in that case, it is the same, just put the url in the src attribute and have no ajax. Only some javascript to update the attribute if appropiate.
Given your comment I understand that you want:
To cache the image in the client to avoid to have to get it back from the server every time.
To allow the user to customize his experience allowing the use of images from local storage.
Now, again for security reasons, arbirary access to client's files is not allowed. In this case it works both ways: first it prevents the webpage to spy you, and second it prevents you to inject malicious content on the page.
So, for the first part, as far as I know the default is to cache images [this is handled by your browser, and yes, you should clean it from time to time because it tends to grow]. If that is not working for you, I guess you could try use a cache manifest.
About the second, the usual way would be use local storage [which, again is handled by your browser, but is not arbitrary access to client's files] to store/retrieve the url of the image and use it present the image.
The image can still be saved at the server, and yes, it can be cached. To get it to the server - of course - you can always upload it with <input type="file" ... /> and you may need to set enctype to your form. - You already knew that, right? - On the server, store the image on a database (or dedicated folder). Now the page that is resposible to retrieve the image should:
check the request method
check user's permissions (identify it by the session / cookie)
check the parameters of the request (if any)
set the header
output the file got the database (or dedicated folder)
Now, let's say you want to allow this to works as an xcopy deployable application (that just happens to run in a browser). In this case you can always tell the user to store the images he want in a particular location and access them with a relative path.
Or - just because - you are hosting in a place were there is no chance of server-side scripting. So you got to go along only with what javascript gives you. Well, you cannot use relative path here, since it is not local... and if you try to use a local absolute path, the browser will just diss you (I mean, it just ignores it).
So, you can't get the image from a file of the client, and you can't store it on the server...
Well, as you know there is a working draft for that, and I notice it is what you are trying. The problem is that it is a working draft. The initial implementation gets staggered by the security issues, to quote Jonas Sicking:
The main problem with exposing this functionality to the web is security. You wouldn’t want just any website to read or modify your images. We could put up a prompt like we do with the GeoLocation API, given that this API potentially can delete all your pictures from the last 10 years, we probably want something more. This is something we are actively working on. But it’s definitely the case here that security is the hard part here, not implementing the low-level file operations.
So, I guess the answer is "not yet"? In fact, considering Microsoft's approach of only providing the parts of the standardar that reach recommendation status, and also its approach of launching a new version of IE each new version of Windows... then you will have to wait a while to have supports in all the browsers. First wait until FileAPI reaches recommendation status. Then wait until Microsoft updates IE to support it. And if, by any chance (as it seems will happen) it will be only for IE10 (or a future IE11) and those deosn't work on a Windows before Windows 8, you will be waiting a lot of people to upgrade.
If this is your situation, I would suggest to get an API for some image hosting web site, and use that instead [That will probably not be free (or not be private), so you could just change your web hosting already].
you cant have a common way to store the response in files compatible with all the browsers ,
there is a way , u can use FileReader in javascript but that again wudn't work on IE either .
I had the similar prob a few weeks ago , what i did was i made an ajax request to a server passing the content , the server stored the content for me in the file , then it return a reference to the stored file.
i stored my files in a temp database table and the server action returned the id for the file by which we can access the file from database whenever we want.
you can also store your files on the server in some thumbnail , but i prefered database.
if u need any more specification , let me know

Categories

Resources