Is it possible to stream an octet stream being generated in javascript? - javascript

Lets suppose a case where a huge string is generated from a small string using some javascript logic, and then the text file is forced to be downloaded on the browser.
This is possible using an octet-stream download by putting it as an href , as mentioned in this answer :
Create a file in memory for user to download, not through server.
function download(filename, text) {
var pom = document.createElement('a');
pom.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
pom.setAttribute('download', filename);
pom.click();
}
But this solution requires 'text' to be fully generated before being pushed for the download,
hence it will have to be held in browser memory fully .
Is it possible to stream the text as it gets generated using CLIENT SIDE LOGIC ONLY ?
For example :
var inputString = "A";
var outStr = "";
for(var i = 0; i < 10000000 ; i++)
{
/* concatenate inputString to output on the go */
}

Yes & no. No because there's not a way to write to files with just client-side javascript. Kinda. You can prompt a user to download & save a file, but as you mentioned, the code must generate the whole file before that download happens. Note: By "stream" I assume you mean stream to file (constantly write to a file) & by "CLIENT SIDE LOGIC ONLY" I assume you mean in the browser.
Looks like Mozilla has been working on a way to let client-side code interact with files. Here comes the yes. Kind of. They have their own file system api that lets you interact with (write to) the local machines file system. Specifically, there's a function that lets you write an input stream to a file. However, there's a few asterisks:
1) looks like that whole system is being deprecated; they encourage developers to use OS.file over File I/O
2) You have to use XPConnect, a system that lets you access Mozilla's XPCOM (component library) in javascript. If you want to do this in the browser, it looks like only firefox extensions have the proper permissions to interact with those components (). If you didn't want to do this in the browser, you obviously could just use node.
Assuredly, more complications are bound to show up during implementation. But this looks like the most sure path forward, seeing as how OS.File gives you access to functions like OS.File.writeAtomic() & basic write to file
That being said, it's not that great of a path, but hopefully this gives you a solid starting point. As #dandavis mentioned, browsers (i.e. "client side logic") are designed to not allow this sort of thing. It would be an incredibly huge oversight / security flaw if a website could interact with any user's local file system.
Additional resources:
Wikipedia on XPConnect
Guide on working with XPCOM in javascript - may not be that useful

There is a way to do this, but it relies on a Chrome only Filesystem API. We will create and write to a temporary file in a sandboxed file system and the copy it to the regular file system once we are done. This way you do not have to store the entire file in memory. The asynchronous version of the Chrome API is not currently being considered for standardization by W3C, but the synchronous verison (which uses web workers) is. If browser support is a concern, then this answer is not for you.
The API works like this:
First, we get the requestFileSystem() function from the browser. Currently it is prefixed by "webkit":
window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;
Next, we request a temporary file system (this way we do not need to ask for user permission):
var fileSystem; //This will store the fileSystem for later access
var fileSize = 1024*1024 //Our maximum file system size.
function errorHandler(e) {
console.log('Error: ' + e.name);
}
window.requestFileSystem(window.TEMPORARY, fileSize, function (fs) { fileSystem = fs; }, errorHandler);
Now that we have access to the file system it is time to create a file:
var fileOptions = {
create: true, //If the file is not found, create it
exclusive: false //Don't throw an error if the file doesn't exist
};
Here we call the getFile() function, which can create a file if it doesn't exist. Inside of the callback, we can create a new fileWriter for writing to the file. The fileWriter is then moved to the end of the file, and we create a new text blob to append to it.
fileSystem.root.getFile(fileName, fileOptions, function(fileEntry) {
fileEntry.createWriter(function(fileWriter) {
fileWriter.seek(fileWriter.length);
var blob = new Blob([STRING_TO_WRITE], {type: 'text/plain'});
fileWriter.write(blob);
}, errorHandler);
});
Note that this API does not save to the normal, user filesystem. Instead, it saves to a special sandboxed folder. If you want to save it to the user's file system, you can create a filesystem: link. When the user clicks on it, it will prompt them to save it. After they save it, you can then remove the temporary file.
This function generates the filesystem link using the fileEntry's toURL() function:
var save = function () {
var download = document.querySelector("a[download]");
if (!fileSystem) { return; }
fileSystem.root.getFile(fileName, {create: false, exclusive: true}, function(fileEntry) {
download.href = fileEntry.toURL();
}, errorHandler);
}
Using a link with the download attribute will force the download of the file.
<a download></a>
Here is a plunker that demonstrates this: http://plnkr.co/edit/q6ihXWEXSOtutbEy1b5G?p=preview
Hopefully this accomplishes what you want. You can continuously append to the file, it won't be kept in memory, but it will be in the sandboxed filesystem until the user saves it to the regular filesystem.
For more information take a look at this HTML5rocks article or this one if you want to use the newer, synchronous Web Worker API.

I would have suggest it the way #quantumwannabe describes it, using temporary sandbox file to append chunks.
But there is a new way that can be used today (behind a flag) but will be enabled in the next version of chrome (52)
And here is where i will make #KeenanLidral-Porter answer incorrect. And #quantumwannabe answer a unnecessary step
Because there is now a way to write a stream to the filesystem directly: StreamSaver.js
It acts as if there was a server sending octet-stream header and tells the browser to download chunks of data with help of a service worker
const writeStream = streamSaver.createWriteStream('filename.txt')
const encoder = new TextEncoder
let data = 'a'.repeat(1024) // Writing some stuff triggers the save dialog to show
let uint8array = encoder.encode(data + "\n\n")
writeStream.write(uint8array) // Write some data when you got some
writeStream.close() // End the saving

Related

How to Launch a PDF from a UWP (Universal Windows Platform) Web Application

I've converted an existing web application (HTML5, JS, CSS, etc.) into a Windows UWP app so that (hopefully) I can distribute it via the Windows Store to Surface Hubs so it can run offline. Everything is working fine, except PDF viewing. If I open a PDF in a new window, the Edge-based browser window simply crashes. If I open an IFRAME and load PDFJS into it, that also crashes. What I'd really like to do is just hand off the PDF to the operating system so the user can view it in whatever PDF viewer they have installed.
I've found some windows-specific Javascript APIs that seem promising, but I cannot get them to work. For example:
Windows.System.Launcher.launchUriAsync(
new Windows.Foundation.Uri(
"file:///"+
Windows.ApplicationModel.Package.current.installedLocation.path
.replace(/\//g,"/")+"/app/"+url)).then(function(success) {
if (!success) {
That generates a file:// URL that I can copy into Edge and it shows the PDF, so I know the URL stuff is right. However, in the application it does nothing.
If I pass an https:// URL into that launchUriAsync function, that works. So it appears that function just doesn't like file:// URLs.
I also tried this:
Windows.ApplicationModel.Package.current.installedLocation.getFileAsync(url).then(
function(file) { Windows.System.Launcher.launchFileAsync(file) })
That didn't work either. Again, no error. It just didn't do anything.
Any ideas of other things I could try?
-- Update --
See the accepted answer. Here is the code I ended up using. (Note that all my files are in a subfolder called "app"):
if (location.href.match(/^ms-appx:/)) {
url = url.replace(/\?.+/, "");
Windows.ApplicationModel.Package.current.installedLocation.getFileAsync(("app/" + url).replace(/\//g,"\\")).then(
function (file) {
var fn = performance.now()+url.replace(/^.+\./, ".");
file.copyAsync(Windows.Storage.ApplicationData.current.temporaryFolder,
fn).then(
function (file2) {
Windows.System.Launcher.launchFileAsync(file2)
})
});
return;
}
Turns out you have to turn the / into \ or it won't find the file. And copyAsync refuses to overwrite, so I just use performance.now to ensure I always use a new file name. (In my application, the source file names of the PDFs are auto-generated anyway.) If you wanted to keep the filename, you'd have to add a bunch of code to check whether it's already there, etc.
LaunchFileAsync is the right API to use here. You can't launch a file directly from the install directory because it is protected. You need to copy it first to a location that is accessible for the other app (e.g. your PDF viewer). Use StorageFile.CopyAsync to make a copy in the desired location.
Official SDK sample: https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/AssociationLaunching
I just thought I'd add a variation on this answer, which combines some details from above with this info about saving a blob as a file in a JavaScript app. My case is that I have a BLOB that represents the data for an epub file, and because of the UWP content security policy, it's not possible simply to force a click on a URL created from the BLOB (that "simple" method is explicitly blocked in UWP, even though it works in Edge). Here is the code that worked for me:
// Copy BLOB to downloads folder and launch from there in Edge
// First create an empty file in the folder
Windows.Storage.DownloadsFolder.createFileAsync(filename,
Windows.Storage.CreationCollisionOption.generateUniqueName).then(
function (file) {
// Open the returned dummy file in order to copy the data to it
file.openAsync(Windows.Storage.FileAccessMode.readWrite).then(function (output) {
// Get the InputStream stream from the blob object
var input = blob.msDetachStream();
// Copy the stream from the blob to the File stream
Windows.Storage.Streams.RandomAccessStream.copyAsync(input, output).then(
function () {
output.flushAsync().done(function () {
input.close();
output.close();
Windows.System.Launcher.launchFileAsync(file);
});
});
});
});
Note that CreationCollisionOption.generateUniqueName handles the file renaming automatically, so I don't need to fiddle with performance.now() as in the answer above.
Just to add that one of the things that's so difficult about UWP app development, especially in JavaScript, is how hard it is to find coherent information. It took me hours and hours to put the above together from snippets and post replies, following false paths and incomplete MS documentation.
You will want to use the PDF APIs https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/PdfDocument/js
https://github.com/Microsoft/Windows-universal-samples/blob/master/Samples/PdfDocument/js/js/scenario1-render.js
Are you simply just trying to render a PDF file?

How to store file in a browser using JavaScript

I am working for a prototype of website(Only Browser based). There is a part where I need to upload some files.Here I am using JavaScript and HTML.
Whenever user wants to upload(Like Browse button in applications) some files then it will available for next time.I am unable to do this.
Question Can we save/store/upload a file using JavaScript/HTML in browser only(Not server )??
Thanks
Downloading file directly to user's file-system
If you by save/store mean directly to user's computer/file system then no, this is not possible due to security reasons.
However, you can initiate a download which will pop up a "save as" type of requester and allow user to accept or deny to download the file.
There are more than one way to initiate a download. An interesting one is the new download attribute for the anchor tag where you can specify a file name as well as automatically initiate the download setting its href to a data-uri or something else you want to reference for download:
Click to download
Local storage mechanisms
If you simply want to save the file locally you can use one of the many local storage mechanisms such as:
File system API (only supported in Chrome currently and the new Opera. In draft status)
Indexed DB (allows Blob objects. Good support)
Web SQL (deprecated but widely in use)
Web Storage (very good support but only stores strings and has limited space, objects can be saved as JSON strings)
Note that all of these as sand-boxed and only available in the browser using the same origin as they was written from. The data may or may not be discarded at any point as well (by user or by browser) so they are not a "safe" storage (always keep a server copy or a way to regenerate the data).
Yes, it's possible via FileSystem API (currently only Chrome and Opera).
window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;
document.querySelector('input[type="file"]').onchange = function(e) {
var files = this.files;
window.requestFileSystem(window.TEMPORARY, 1024*1024, function(fs) {
// Duplicate each file the user selected to the app's fs.
for (var i = 0, file; file = files[i]; ++i) {
// Capture current iteration's file in local scope for the getFile() callback.
(function(f) {
fs.root.getFile(f.name, {create: true, exclusive: true}, function(fileEntry) {
fileEntry.createWriter(function(fileWriter) {
fileWriter.write(f); // Note: write() can take a File or Blob object.
}, errorHandler);
}, errorHandler);
})(file);
}
}, errorHandler);
};
"Can we save/store/upload a file using JavaScript/HTML in browser only(Not server )??"
Ans. is No. If you want to retain the uploaded file then you need to store it on server.
The moment the browser tab is closed the file will get lost.
Alternative: What you can do is store the name of the file on server and whenever user request the file then ask him to upload it.
For uploading the file into memory you can refer to this link .
Live Demo

HTML5 FileSystem API

I have created file 'log.txt' by fileSystem API
function initFS(grantedBytes) {
window.requestFileSystem(window.PERSISTENT, grantedBytes, function (filesystem) {
fs = filesystem;
fs.root.getFile('log.txt', { create: true, exclusive: true }, function (fileEntry) {
// fileEntry.isFile === true
// fileEntry.name == 'log.txt'
// fileEntry.fullPath == '/log.txt'
console.log(fileEntry.fullPath);
}, errorHandler);
}, errorHandler);
}
initFS(1024*1024);
And do not fully understand its structure. Is There any way to explore this file
for example from Windows Explorer and see it in file system?
There is an even simpler way. On chrome, visit these urls.
For http, it's "filesystem:http://"+location.host+"/persistent/".
For https, it's "filesystem:https://"+location.host+"/persistent/".
Sort of, the File-system API doesn't encrypt the data being stored locally. It does however change the file naming conventions up. So you may have named it log.txt but if you poke around where the file-system API stores files, you'd probably find it under some arbitrary randomly generated file name like "00010" or in a random directory like "24/00123".
Anyway, you can open each file up in a text editor - if your file had text written to it you would be able to view it as such. Or if you wrote JSON to the file-system API it would be in human-readable string format when you opened in the text editor.
On Windows 7, with Chrome it's found here:
C:\Users\{user}\AppData\Local\Google\Chrome\User Data\Default\File System\
If you want to find out where it is stored via Chrome on other OS please see this post
Log files that an end-user or maintainer might want to see should be stored someplace in the normal file system. While the checked answer suggests how to find them when the HTML5 API is used, this location is subject to change and is troublesome to find.
A better solution is to have the user choose the directory for log files (and perhaps other files) when the app is installed, using chrome.fileSystem.chooseEntry, and then retain that entry and save it in local storage so it can be reused on subsequent launches.

Save client generated data as file in JavaScript in chunks

I'm developing a FileShare application with webRTC. I want to implement the client in JavaScript/HTML. The code should be run on the clients browser.
I need to save them when downloaded via webRTC. The files can be quite big and I can't completely donwload them and save them in a array or blob before saving them to disk as a file.
Is there any API that allows me to save the file in chunks as I recieve them?
I have found so far Downloadify, FileSave.js and html5 FileWriterApi so far.
While the first two are not chunked and require me to first download the complete file to memory before saving, the FileWriterAPI is not available on most browsers.
As #jordan-gray suggested, saving the chunks in blobs and joining them to a larger blob might be a solution if:
Persistence of chunks is not needed (i.e. closing the browser will delete all chunks)
The file is persisted only by the user saving it to his own filesystem. The web application will not have access to the file once it is closed, unless the user gave access to the saved file again.
Possibly, if the file sizes are not too big (you'll have to benchmark to find that out). Chrome was behaving quite nice for me for chunks totaling at 1GB.
I've created a simple test for using blobs as chunks. You can play around with the different size and chunk numbers parameters:
var chunkSize = 500000;
var totalChunks = 200;
var currentChunk = 0;
var mime = 'application/octet-binary';
var waitBetweenChunks = 50;
var finalBlob = null;
var chunkBlobs =[];
function addChunk() {
var typedArray = new Int8Array(chunkSize);
chunkBlobs[currentChunk] = new Blob([typedArray], {type: mime});
console.log('added chunk', currentChunk);
currentChunk++;
if (currentChunk == totalChunks) {
console.log('all chunks completed');
finalBlob = new Blob(chunkBlobs, {type: mime});
document.getElementById('completedFileLink').href = URL.createObjectURL(finalBlob);
} else {
window.setTimeout(addChunk, waitBetweenChunks);
}
}
addChunk();
If you do need that persistence, the W3C File System API should support what you need. You can use it to write the chunks to separate files, and then when all the chunks are completed you can read them all and append them to a single file, and remove the chunks.
Note that it works by assigning a sandboxed filesystem for your application (for a given quota), and the files are only accessible to that application. If the files are meant to use outside of the web application, you might need the function for the use to save the file from the application filesystem to his "normal" filesystem. You can do something like that using the createObjectURL() method.
You are right about current state of browser support. A Filesystem API polyfill is available, which is based on IndexedDB (which is more widely supported) as a filesystem emulation backend. I did not test the polyfill on large files. You might run into size limits or performance limitations.
Did you check https://github.com/Peer5/Sharefest out ? It should cover your requirements

Chrome extension: How to save a file on disk

I'm currently creating an extension for google chrome which can save all images or links to images on the harddrive.
The problem is I don't know how to save file on disk with JS or with Google Chrome Extension API.
Have you got an idea ?
You can use HTML5 FileSystem features to write to disk using the Download API. That is the only way to download files to disk and it is limited.
You could take a look at NPAPI plugin. Another way to do what you need is simply send a request to an external website via XHR POST and then another GET request to retrieve the file back which will appear as a save file dialog.
For example, for my browser extension My Hangouts I created a utility to download a photo from HTML5 Canvas directly to disk. You can take a look at the code here capture_gallery_downloader.js the code that does that is:
var url = window.webkitURL || window.URL || window.mozURL || window.msURL;
var a = document.createElement('a');
a.download = 'MyHangouts-MomentCapture.jpg';
a.href = url.createObjectURL(dataURIToBlob(data.active, 'jpg'));
a.textContent = 'Click here to download!';
a.dataset.downloadurl = ['jpg', a.download, a.href].join(':');
If you would like the implementation of converting a URI to a Blob in HTML5 here is how I did it:
/**
* Converts the Data Image URI to a Blob.
*
* #param {string} dataURI base64 data image URI.
* #param {string} mimetype the image mimetype.
*/
var dataURIToBlob = function(dataURI, mimetype) {
var BASE64_MARKER = ';base64,';
var base64Index = dataURI.indexOf(BASE64_MARKER) + BASE64_MARKER.length;
var base64 = dataURI.substring(base64Index);
var raw = window.atob(base64);
var rawLength = raw.length;
var uInt8Array = new Uint8Array(rawLength);
for (var i = 0; i < rawLength; ++i) {
uInt8Array[i] = raw.charCodeAt(i);
}
var bb = new this.BlobBuilder();
bb.append(uInt8Array.buffer);
return bb.getBlob(mimetype);
};
Then after the user clicks on the download button, it will use the "download" HTML5 File API to download the blob URI into a file.
I had long been wishing to make a chrome extension for myself to batch download images. Yet every time I got frustrated because the only seemingly applicable option is NPAPI, which both chrome and firefox seem to have not desire in supporting any longer.
I suggest those who still wanted to implement 'save-file-on-disk' functionality to have a look at this Stackoverflow post, the comment below this post help me a lot.
Now since chrome 31+, the chrome.downloads API became stable. We can use it to programmatically download file. If the user didn't set the ask me before every download advance option in chrome setting, we can save file without prompting user to confirm!
Here is what I use (at extension's background page):
// remember to add "permissions": ["downloads"] to manifest.json
// this snippet is inside a onMessage() listener function
var imgurl = "https://www.google.com.hk/images/srpr/logo11w.png";
chrome.downloads.download({url:imgurl},function(downloadId){
console.log("download begin, the downId is:" + downloadId);
});
Though it's a pity that chrome still doesn't provide an Event when the download completes.chrome.downloads.download's callback function is called when the download begin successfully (not on completed)
The Official documentation about chrome.downloadsis here.
It's not my original idea about the solution, but I posted here hoping that it may be of some use to someone.
There's no way that I know of to silently save files to the user's drive, which is what it seems like you're hoping to do. I think you can ASK for files to be saved one at a time (prompting the user each time) using something like:
function saveAsMe (filename)
{
document.execCommand('SaveAs',null,filename)
}
If you wanted to only prompt the user once, you could grab all the images silently, zip them up in a bundle, then have the user download that. This might mean doing XmlHttpRequest on all the files, zipping them in Javascript, UPLOADING them to a staging area, and then asking the user if they would like to download the zip file. Sounds absurd, I know.
There are local storage options in the browser, but they are only for the developer's use, within the sandbox, as far as I know. (e.g. Gmail offline caching.) See recent announcements from Google like this one.
Google Webstore
Github
I made an extension that does something like this, if anyone here is still interested.
It uses an XMLHTTPRequest to grab the object, which in this case is presumed to be an image, then makes an ObjectURL to it, a link to that ObjectUrl, and clicks on the imaginary link.
Consider using the HTML5 FileSystem features that make writing to files possible using Javascript.
Looks like reading and writing files from browsers has become possible. Some newer Chromium based browsers can use the "Native File System API". This 2020 blog post shows code examples of reading from and writing to the local file system with JavaScript.
https://blog.merzlabs.com/posts/native-file-system/
This link shows which browsers support the Native File System API.
https://caniuse.com/native-filesystem-api
Since Javascript hitch-hikes to your computer with webpages from just about anywhere, it would be dangerous to give it the ability to write to your disk.
It's not allowed. Are you thinking that the Chrome extension will require user interaction? Otherwise it might fall into the same category.

Categories