3

I am using Brackets-shell to build a desktop packaged app for Windows and OSX. The application that runs on Brackets shell is able to open dynamically generated Excel sheets and PDF files from a server. In a normal browser, the request to the file is done in a hidden iFrame that allows the user to save the file. In Brackets shell however, the document is not loaded. Instead it returns the following error in the iFrame

Failed to load URL http://my.server.com/generate/test.pdf with error (-3)

Can anyone tell me how to make Brackets download and save the file on the local FS? I have already tried brackets.app.openURLInDefaultBrowser but that won't work since the default window does not have the authentication cookies the application uses.

Thanks

yunzen
  • 32,854
  • 11
  • 73
  • 106
Rob Boerman
  • 2,148
  • 14
  • 21

2 Answers2

2

I think this is not (yet) supported by brackets-shell -- it appears that CEF-based apps have to specifically implement a "download handler" which brackets-shell hasn't done. This AppJS bug comment says much the same (as that shell is also CEF-based). The suggestion there to use Node for the download would work with brackets-shell too, but you'd run into the same problem with auth cookies as you had with openURLInDefaultBrowser().

If you were downloading text-only content, it could be done entirely with brackets-shell's appshell.fs file IO APIs, which would fix the cookie situation. But those APIs currently only handle UTF-8 text.

So I think you'll need to patch brackets-shell to implement the download functionality. The starting point would be to implement a CefDownloadHandler and return it from CefClient::GetDownloadHandler. It looks like CEF takes care of the Save As dialog for you (see show_dialog arg of callback), so you might not need to write much code at all.

peterflynn
  • 4,667
  • 2
  • 27
  • 40
1

This can be done using Node look at: https://github.com/adobe/brackets/wiki/Brackets-Node-Process:-Overview-for-Developers#Architecture

starts around the header: Step 0: Setting up the extension.

In summary you can communicate with node in brackets on a websocket. With this you can load node modules dynamically as well as execute them.

Now these instructions can be pretty heavy if you are not using require. If you are, then stop here and use those instructions. If you are not using require you can "un"require the code and strip out what you need. A project I am on had to do this because on how late we realized brackets didn't support pdf downloads.

So below are some instructions that will hack out what you need into your application. Again this is a deconstruction of some pretty good code.

WARNING: This is not pretty and is not the brackets way to get this working.

WARNING WARNING Seriously

Create your node save module

This is done the same as in the instructions above and will still use require however its loaded into node so you don't have to worry about require for the application part of your project. Save code is similar to this: https://github.com/joelrbrandt/brackets-simple-node/blob/master/node/SimpleDomain.js

(function () {
    "use strict";
    var url = require('url');
    var http = require('http');
    var https = require('https');
    var fs = require('fs');

    function cmdSaveFileFromUrl(urlString, dest) {
        var requestUrlObject = url.parse(urlString, true);
        var options = {
            hostname: requestUrlObject.hostname,
            port: +requestUrlObject.port,
            path: requestUrlObject.path
        };
        var requester = requestUrlObject.protocol === "http:" ? http : https;
        var file = fs.createWriteStream(dest);
        var request = requester.get(options, function(response) {
            response.on('end', function(){
                file.on('finish', function() {
                    file.close();
                });
            });
            response.pipe(file);
        });
    }

    function init(DomainManager) {
      ...
    }
    exports.init = init;
}());

dynamically load this module

Now you need to get the appshell code that will dynamically load this module. For this you need basically the entire NodeConnection module from brackets. This module is responsible for making the websocket to node work. So if you don't have require like I did you can "un"require it. rip out the export stuff and just let it return itself, include this in your index.html.

function NodeConnection() {
    "use strict";
....
    return NodeConnection;
}

Code to call the node connection

Last you need to use the node connection to load the module and send the command to perform its task, again brackets-shell (appshell) provides modules for this but again if you don't have require you need to pull out what you need. Most of the methods at utility methods and can be pulled out pretty easily. https://github.com/joelrbrandt/brackets-simple-node/blob/master/main.js

From anywhere:

function chain() {
    var functions = Array.prototype.slice.call(arguments, 0);
    if (functions.length > 0) {
        var firstFunction = functions.shift();
        var firstPromise = firstFunction.call();
        firstPromise.done(function () {
            chain.apply(null, functions);
        });
    }
}
function connect() {
    var connectionPromise = nodeConnection.connect(true);
    connectionPromise.fail(function () {
        console.error("[brackets-node] failed to connect to node");
    });
    return connectionPromise;
}
function getNativeBracketsDirectoryPath() {
    var pathname = decodeURI(window.location.pathname);
    var directory = pathname.substr(0, pathname.lastIndexOf("/"));
    return convertToNativePath(directory);
}
function convertToNativePath(path) {
    path = unescape(path);
    if (path.indexOf(":") !== -1 && path[0] === "/") {
        return path.substr(1);
    }

    return path;
}
function loadDomain() {
   var root_url = getNativeBracketsDirectoryPath();
   var path = root_url + "/node/YOUR_NODE_MODULE_FROM_STEP_ONE.js";
   var loadPromise = nodeConnection.loadDomains([path], true);
   loadPromise.fail(function () {
       console.log("[brackets-node] failed to load domain");
   });
   return loadPromise;
}
function saveFile(inUrl, path) {
    path = path || window.appshell.app.getUserDocumentsDirectory()+"/file";
    var memoryPromise = nodeConnection.domains.yourdomain.saveFileFromUrl(inUrl, path);
    memoryPromise.fail(function (err) {
        console.error("[brackets-node] failed to run saveFileFromUrl", err);
    });
    memoryPromise.done(function () {
                    console.log("Check the file");
    });
    return memoryPromise;
}
var  NodeConnectionObject = NodeConnection();
var nodeConnection = new NodeConnectionObject();
chain(connect, loadDomain, function(){
   return saveFile(urlToFetch, file)
});

I hope this helps someone.

Chris Stephens
  • 2,164
  • 2
  • 17
  • 21