Run External Application from XD Plugin

#4

@overflowapp, @stevekwak thank you for such prompt response.
But what if your app is not running? Is there any way to run this app from plugin?

0 Likes

#5

Unfortunately, there is no way to fire up the app.
We are too looking forward to this :slight_smile:

1 Like

#6

@stevekwak maybe you have ideas how Zepplin application handles this case (cause I can see that Zeplin launches its application even if it’s closed)? Maybe there is some URL launcher in Adobe XD API (so I could just map my app to custom URL and then open it)?

0 Likes

#7

Zeplin doesn’t actually have any plugins but is a “direct” integration (which isn’t a public API). There is a URL launcher (shell.openExternal, see https://adobexdplatform.com/plugin-docs/reference/uxp/class/Shell.html#shellopenexternalurl), but this only works for the HTTP(S) protocol (and not for “custom” protocols like file://, abc:// etc.).

There is – in fact – currently no way to launch an external application with an Adobe XD plugin (this would also mean many security risks, which is why it also shouldn’t be as easy as “just launching” something).

You could –e.g. – check if the app is running and otherwise promt the user to start it if it doesn’t.

2 Likes

#8

Sounds reasonable. Thank you for prompt and comprehensive answer.

1 Like

#9

On Mac you can set a link in a UI control that when clicked will launch with the file: protocol and launch the registered application.

This works on Mac:

// open page on the file system
var openLocationLink = h("a", {href:""}, "Open");
openLocationLink.setAttribute("href", "file:///Users/user/documents/project/page.html");

// open page in your plugin directory
var folder = await fileSystem.getPluginFolder();
var file = await folder.getEntry("instructions.html");
openLocationLink.setAttribute("href", "file://" + file.nativePath);

// open a folder in Finder
var folder = await fileSystem.getPluginFolder();
openLocationLink.setAttribute("href", "file://" + folder.nativePath);

I haven’t had this work on Windows.

But if the file type isn’t registered you can launch the browser and use that to redirect to a page in your plugin directory to redirect to a registered application types. Apple does this with iTunes for links to podcasts or apps. A window pops up and asks if you want you want to launch the registered link.

There are security concerns sure but they have chosen to install your plugin so there is a level of trust you have granted and in that there is a trust that an application you attempt to open would have to do with your plugin.

There has been discussion around the openExternal in the past and @kerrishotts might have some updated information on this.

2 Likes

Is this correct to use image fill
#10

Just to clarify: I’m not saying it should be impossible. I’m just saying this shouldn’t be as easy as just using a simple command like openExternal(). For example, there could be some sort of way where the user gets prompted if link xy should get openend.

The thing is that it would be very easy to get harmful stuff into the plugin if Adobe isn’t extremely careful here (also, there could be things like an API activating it after review by Adobe is already done), and there are always some black sheep. If Adobe isn’t extremely careful, this could potentially lead to a big damage to the image of plugins and/or even Adobe XD in general (you could even dynamically download your harmful files so you don’t have to get them through review :slightly_frowning_face:).

Therefore, while I agree there is a certain level of trust by users installing the plugin, this shouldn’t get taken “as easily” as saying “there’s some level of trust, so just do it” :wink:

0 Likes

#11

Hi @Velara,

probably I’m missing something to make it work (I’m on Mac)

var openLocationLink = h(“a”, {href:""}, “Open”);

what does that ‘h’ mean?

0 Likes

#12

@PaoloBiagini It’s some sort of micro-library for creating DOM objects (cf. https://github.com/AdobeXD/plugin-samples/tree/master/ui-hello-h).

Edit: It also seems to be known (if that’s the same, I’m not quite sure :wink:) under the name ‘hyperscript’ (see https://github.com/hyperhype/hyperscript).

1 Like

#13

thank you very much @pklaschka, I will check that as soon as possible.

1 Like

#14

Wow – lots of great discussion on here… apologies for not getting in on this earlier, but it’s been a hectic week!

Anyway – to the points here, this is something we do have to be very careful about. It’s not so much specific developers we’re worried about here (after all, I don’t think anyone on this forum would do something malicious to their users), but more in the aggregate, and we do have to keep in mind that arbitrary code comes with considerable risks and the user is rarely in an educated position to make that determination of trust. (As we can see from the browsers and their continued refinement of secure vs unsecure pages, forms, submissions, etc.)

Specifically, when it comes to running an external application from XD, we have the following issues:

  • Security risks – already mentioned in these threads
  • Privacy concerns – access to low level information could potentially breach the user’s privacy in ways our current API surface hasn’t considered.
  • Cross-platform concerns – plugins that launch external programs are that much harder to write in a cross-platform manner.
  • Permissions and UX – how do we communicate the attempt to the end user to get access (or do we at all), and if so, how do we ensure that users have the information they need to make an educated decision. (And then, what happens if the user rejects the attempt?)

None of these are necessarily insurmountable, but we have to be very careful here. Right now we are exploring what this would look like, but I’ve no additional news to share at this point precisely because there are huge boulders to address.

For now, I think the best thing the community can do is to continue submitting specific use cases – as in, why do you need to launch a particular external application (the Zepplin example is a good point)? That knowledge will hopefully lead us to a better solution that can be done in a secure, cross-platform, user-trustable manner.

3 Likes

#15

That’s about right but it is only a single function that is used in some of the plugin examples to create elements for your UI.

It’s the same as calling createElement, setting properties then adding any nested elements. It’s useful in that it may be easier to read.

In fact here is the whole function:

function h(tag, props, ...children) {
    let element = document.createElement(tag);
    if (props) {
        if (props.nodeType || typeof props !== "object") {
            children.unshift(props);
        }
        else {
            for (let name in props) {
                let value = props[name];
                if (name == "style") {
                    Object.assign(element.style, value);
                }
                else {
                    element.setAttribute(name, value);
                    element[name] = value;
                }
            }
        }
    }
    for (let child of children) {
        element.appendChild(typeof child === "object" ? child : document.createTextNode(child));
    }
    return element;
}

And here it is creating an Alert dialog:

let alertDialog =
	 h("dialog", {name:"Alert"},
		h("form", { method:"dialog", style: { width: 380 }, },
		  alertForm.header = h("h1", "Header"),
		  h("label", { class: "row" },
			 alertForm.message = h("span", { }, "Message"),
		  ),
		  h("footer",
			 h("button", { uxpVariant: "cta", type: "submit", onclick(e){ closeDialog(alertDialog) } }, "OK")
		  )
		)
	 )
2 Likes

#16

many thanks for all explanations!

I directly inserted the h function inside my main.js but, calling this function the folder doesn’t open in Finder (‘exportFolder’ being set early in the code)

function openExportFolder()
{
    var openLocationLink = h("a", {href:""}, "Open");
    openLocationLink.setAttribute("href", "file://" + exportFolder.nativePath);

    console.log(exportFolder.nativePath);
 }

though exportFolder.nativePath printed in the console is right.

0 Likes

#17

This is not the same as shell.openExternal() and you don’t have to use h.

You have to create a hyperlink in your plugin’s UI and then when the user clicks on it it will open your folder.

So something like this:

/**
* Shorthand for creating Elements.
* @param {*} tag The tag name of the element.
* @param {*} [props] Optional props.
* @param {*} children Child elements or strings
*/
function h(tag, props, ...children) {
    let element = document.createElement(tag);
    if (props) {
        if (props.nodeType || typeof props !== "object") {
            children.unshift(props);
        }
        else {
            for (let name in props) {
                let value = props[name];
                if (name == "style") {
                    Object.assign(element.style, value);
                }
                else {
                    element.setAttribute(name, value);
                    element[name] = value;
                }
            }
        }
    }
    for (let child of children) {
        element.appendChild(typeof child === "object" ? child : document.createTextNode(child));
    }
    return element;
}

function onsubmit() {
    //  dialog is automatically closed after submit unless you call e.preventDefault()
}


let labelWidth = 75;
let hyperlinkLabel = null;
let dialog =
    h("dialog",
        h("form", { method:"dialog", style: { width: 380 }, onsubmit },
            h("h1", "Export Complete"),

            h("label", { class: "row", marginTop:10 },
                h("span", { style: { width: labelWidth, flex: "1" } }, "Export completed successfully!")
            ),
                
            h("footer",
                h("label", { class: "row", paddingTop:0, style: {border:"0px solid #888888",  alignItems:"center"} },
                    hyperlinkLabel = h("a", { href:"",
                        style: {
                            textAlign: "center", marginTop:"0", position:"relative", top:"0", height:23, paddingTop:"4.5",
                            flex: "1" , backgroundColor:"#2D96F0", border:"0px solid #888888", verticalAlign:"middle",
                            paddingLeft:"16", paddingRight:"16", marginRight:"0", borderRadius: "12",
                            fontSize:"11px", fontWeight:"700", color:"#FFFFFF"
                        }
                    }, "Reveal in Finder")
                ),
                h("button", { uxpVariant: "cta", type: "submit", onclick(e){ onsubmit(); dialog.close(); e.preventDefault; } }, "Close")
            )
        )
    )
    
async function openAlert() {
    const fileSystem = require("uxp").storage.localFileSystem;
    document.body.appendChild(dialog);
    dialog.showModal();
    const pluginFolder = await fileSystem.getPluginFolder();
    var path = "file://" + pluginFolder.nativePath;
    hyperlinkLabel.href = path;
    hyperlinkLabel.title = path;
}

module.exports = {
    commands: {
        menuCommand: openAlert
    }
};

Manifest:

{
    "id": "UI_OPEN_IN_FINDER",
    "name": "(UI) Open in Finder",
    "version": "1.0.0",
    "host": {
        "app": "XD",
        "minVersion": "13.0.0"
    },
    "uiEntryPoints": [
        {
            "type": "menu",
            "label": "Open in Finder",
            "commandId": "menuCommand"
        }
    ]
}

OpenInFinder.xdx (2.7 KB)

0 Likes

#18

Using hyperlink works, thank you @Velara!
Too bad we can’t use any hover/click effect to simulate a button.

2 Likes

#19

@PaoloBiagini Did you test this on Windows? Just as a “warning”: I once tried to achieve this and it didn’t work on WIndows, so you might have to test if this really works on Windows machines (please note: I’m not saying it doesn’t work – I could also have done something wrong when I tried it – I’m just saying you’ll need to test it thoroughly :wink:) .

0 Likes

#20

hi @pklaschka, thank you.
unfortunately I can’t make any test on a Windows system at the moment.
Did you get a warning in the console?

0 Likes

#21

@PaoloBiagini I didn’t get a chance to test it, yet (I will do so later today and then write again). However, the main thing is that this wouldn’t give you any messages, but just do nothing when the button/link gets clicked…

0 Likes

#22

I’ve just tested it on Windows: unfortunately, clicking the button nothing happens; even no message in the console.

2 Likes

#23

any progress ? on this work around on windows

0 Likes