How to handle File Activation in Windows Store / WinJS apps - javascript

I'm trying to make my text editor app handle a file launch. Microsoft has a sample of how to do this here:
http://msdn.microsoft.com/en-us/library/windows/apps/hh452684.aspx
Unfortunately, it stops at the point of receiving the file and doesn't give any info on how to actually open said file.
I can successfully handle the activated event, and I end up with an absolute path to the file. For example,
C:\Users\Rory\Documents\test.txt
Metro apps don't have permission to access absolute paths, except under certain conditions.
if the file is chosen by the user via a file picker
if the app has accessed the file before and the path has been stored in the Windows.Storage.AccessCache
if the app is being passed the file as a launch.
Even though number 3 applies in this case, I can't open the file.
I've tried Windows.Storage.StorageFile.getFileFromPathAsync(path_to_file) but I get this error
0x80070005 - JavaScript runtime error: Access is denied.
WinRT information: Cannot access the specified file or folder (඀6).
The item is not in a location that the application has access to (including
application data folders, folders that are accessible via capabilities
and persisted items in the StorageApplicationPermissions lists). Verify
that the file is not marked with system or hidden file attributes.
I've set my app package manifest to accept txt files already.

The StorageFile or StorageFiles are passed to your app in the WebUIFileActivatedEventArgs argument. Try this:
app.onactivated = function (args) {
if (args.detail.kind === activation.ActivationKind.file) {
if (args.detail.files.size > 0) {
var storageFile = args.detail.files[0];
Windows.Storage.FileIO.readTextAsync(storageFile).then(function (text) {
// Do something with the content of the file.
});
}
}
// ...
}

Related

Is it possible to exchange pathnames between Webview2 and native code?

I have a C++ app that uses WebView2 as UI component. Native-side code and web-side code communicate via a host object. That works great, but:
I want to let the user (for example) drag and drop files to a drop box on the UI, hand the path names over to the client C++ app, which should read and process the files. Butfor this I would need the full path names (on the host).
I have currently no example for the other way round, but I could imagine to hand a path name to JavaScript which should the read and process this file.
I couldn't find any documentation neither on WebView2, nor in the File Aystem Access API on the Browser side (Java Script).
I know that there are security issues for real web apps, but - hey - this is a native component anyway!
So here is my code in JavaScript:
butOpenFile.addEventListener('click', async () => {
// Destructure the one-element array.
[fileHandle] = await window.showOpenFilePicker();
// Do something with the file handle.
});
How can I use *fileHandle *to retrieve the full path of the selected file, that the hostObject can use to open and process the file? (Note: fileHandle.name only has the name.ext-part of the path)

Android webview - how to fetch a file from external storage

I have a mobile app that wraps around the web-app, using webview.
The web-app has a button to open a large .zip file (e.g. 100 MB).
The user clicks a button, and selects a .zip file.
This triggers an onChange function with a variable of type File (Blob), which includes attributes like:
file name
file size
file type (application/zip)
The javascript code then parses the .zip file, extracts specific data within it and uses it within the web-app.
This works well within the web-app, when the app is called via the Chrome browser.
For example when operated in chrome browser on an Android phone, I can pull the .zip file and open it in the web-app.
I want to do the same but using the mobile app.
I am able to pick up the .zip file using a File Chooser, and pass it to Webview but I have problems to fetch the file from the Javascript code.
For reference, I am able to pass an image, by creating a data_uri using stringBuilder and passing the content (as data:image/jpeg;base64).
But the zip file is much larger.
When calling fetch(fileUri) from the Javascript side I'm getting errors.
I'm using the following uri
/storage/emulated/0/Android/data/com.example/files/Download/file2.zip
The fetch succeeds but returns a blob with size of 165 (i.e. not the actual size of the file) which hosts the error message:
{
"error": "Not Found",
"message": "The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again."
}
The program flow is like so:
I select a .zip file via FileChooser.
In onActivityResult, the uri value is /document/msf:12858 (seen via uri = intent.getData();)
The uri needs to be mapped into a real path file url, such that the fileUrl will be passed to webview.
Webview will then fetch the file using the fileUrl.
I searched how to get the real path file url when selecting a file with FileChooser, and found
this, and this links.
I wasn't able to get the real file path, so I decided to read the file and write it to another location, so I can get a file path. (this is not efficient and done just to check the functionality).
I create the new file using the following code:
InputStream stream = context.getContentResolver().openInputStream(uri);
File file2 = new File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "file2.zip");
writeBytesToFile(stream, file2);
I don't see any errors when creating the file, and when creating the file, the number of bytes that are read and written to the new file are as expected.
For file2, I get a value of:
/storage/emulated/0/Android/data/com.example/files/Download/file2.zip
Then, within the Javascript code I fetch this file path.
But I'm getting a Blob with the "file-not-found" content as above.
So:
How can I verify that the file is indeed created and that the path can be fetched from webview?
How can I get the real file path of the original selected file, so I don't have to read and write the original file to new location just to get the file path?
Thanks
I was able to get the file from external storage by doing the following steps:
create an initial uri (uri1)
The uri is created by:
creating a temporary file (file1) in the storage dir via
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)
I'm not sure why a temporary file need to be created but if I don't create a file I cannot get the uri.
createFile3
get the uri via
Uri uri1 = FileProvider.getUriForFile(context, "com.example.android.fileprovider", file1);
create an intent with the following attributes:
Intent.ACTION_OPEN_DOCUMENT
category: Intent.CATEGORY_OPENABLE
type: "application/zip"
extra attribute: fileIntent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, uri1);
this opens a dialog box for selecting openable zip files in the Downloads directory,
after the file is selected, a new uri (uri2) is created that includes the name of the selected file.
extract the name of the file via
String fileName = getFileName(context, uri2);
create the dirPath by appending the filename
dirPath = "/data/user/0/com.example/" + fileName;
if the dirPath does not exist (first time), write the file to its dirPath location.
on successive ocassions dirPath exists, so there is no need to re-write the file.
open the file with regular Java means, e.g. via
ZipFile zip = new ZipFile(dirPath);

Using javascript client-side, how can I display a "virtual folder" (= a coherent collection of html,img,js with relative paths)?

I want to display autonomous "sites" on a html page (Say "root").
Those "sites" contain a landing page : index.html and a collection of *.css, .js,.png)
By autonomous, I mean those sites does not have external dependencies and all paths are relative = you can copy them in any directory or host them anywhere and they'll work.
Those sites are zipped in a archive that contains all necessary files.
Say I got no problem with the download and got all the files in memory (as path/uint8 array)
How could I display the site in a safe way ?
I can parse the index.html, change all the src and href for data-url of the original files and load it in an iframe.
It works rather well but it breaks where there are scripts like this
if (extension == "pdf")
img.src = "images/thumb-pdf.png"
Is there any way to control the url served by the iframe?
Some kind of proxy?
Can I intercept "images/thumb-pdf.png" to serve MemoryCacheOfAllFiles["images/thumb-pdf.png"].toDataURL() instead ?
PS: Of course I got no control on those sites and I can't store them on server (it would be to easy)
Proxy are possible in javascript one got to use a worker and listen for the "fetch" event to push the virtual content.
html pushed in iframe:
...
navigator.serviceWorker.register('worker.js').then(function() {
logInstall('Installing...');
})
...
worker.js:
...
self.onfetch = function(event) {
var cached = (mycache[event.request.url]); // do we have content
if (cached)
event.respondWith(new Response(cached.content,{
headers:{"Content-Type",cached.mimetype}
}
else
event.respondWith(fetch(event.request));
}
...
Full code by mozilla to unzip and serve content
https://serviceworke.rs/cache-from-zip.html

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?

WinJs creating files on the fly in installedLocation

I am developing WinJs app and I want to create several files in my app installed location in order to navigate to them locally. When I am trying to create new file I am getting Access denied exception :
"WinRTError: Access is denied"
This is the code which I'm using for file creation:
var folder = Windows.ApplicationModel.Package.current.installedLocation;
folder.createFileAsync("index.html", Windows.Storage.CreationCollisionOption.replaceExisting)
Is there a way to allow this functionality, or is it just blocked for security reasons and there's nothing that you can do about it?
Craeting those files in local folder cause another issue that I want to prevent - that's why I am trying to create them in installed location.
Thanks
You can't do that ! this folder is a read-only.
But ... (if you have to do this)
You can write anithing in the localFolder
Windows.ApplicationModel.Package.current.LocalFolder
And write a lot of code to read this files that you created, to load them dynamically in your app

Categories