Note 1-Nov-2020: Make sure to check out my novel approach to GetURL: making curl available in ExtendScript. Not just for InDesign – any Adobe app that supports ExtendScript can use this. More info here:
https://www.rorohiko.com/jsxgeturl
Note 16-Dec-2019: Latest installer which also supports InDesign 2020 and macOS Catalina:
https://www.rorohiko.com/downloads/ExtendExtendScript.1.0.4.zip
GetURL: some history
A long, long time ago, I wrote a bit of sample code to demonstrate state machines.
On a whim, I decided to implement a bit of the HTTP protocol in ExtendScript and InDesign.
It was never intended to be a full HTTP socket implementation, but in the end, it turned out that the sample served a purpose for many people, as it allowed them to interact with HTTP-based services from ExtendScript.
It was not very fast, and could not handle many HTTP features.
http://rorohiko.blogspot.co.nz/2013/01/geturlsjsx.html
Over time, I added a few little tweaks to help people out, but by now GetURL.jsx must be a contender for the longest living stopgap measure ever.
HTTPS?
Recently, more and more people started asking me for HTTPS support.
Sadly enough, crusty old GetURL.jsx is not up to the task: for efficient HTTPS support, things get a lot more complicated.
Doing it all in pure ExtendScript would be nearly certainly too slow.
So, I decided to find a different solution.
What I am presenting here is ‘work-in-progress’, or maybe better ‘mucking-around-in-progress’.
I am playing around, and try things out. I will probably be tweaking this for some time to come, but it currently works fairly well as-is, so for people in need, they can grab what I have, as-is, and try to make sense of it.
In the following post, I’ll show a solution that allows the ExtendScript coder to write something as simple as
#targetengine TestSomewhere #include "JSInterface.jsx" JSInterface.evalScript("console.log('hello');"); // or... function handleData(data) { alert(data); } JSInterface.evalScript( "JSInterface.plugins.getURL('https://www.rorohiko.com/welcome.msg')", handleData); // or... var url = "https://www.rorohiko.com/welcome.msg"; JSInterface.evalScript( "JSInterface.plugins.getURL(JSInterface.getData())", url, handleData);
Bear with me…
CEP
For a while now, InDesign and other Creative Cloud apps have been including the Adobe Common Extensibility Platform (CEP) and Extensions.
Essentially, an Extension is akin to a browser (Chrome) with a server (Node.js) all rolled into one, all running as an ancillary process inside of InDesign.
As a result, an InDesign scripter now has two totally separate programming environments at their disposal.
These environments share very similar languages (ExtendScript and JavaScript), which is often a bit confusing.
The ExtendScript (ES) programming environment is ‘inward facing’: it is tightly integrated with InDesign and can interact with the InDesign Document Object Model (DOM).
The JavaScript (JS) programming environment is ‘outward facing’: it can drive user-interfaces that are contained within floating panels and dialog boxes.
Because the JS environment has a complete Node.js available, writing things like an https-capable GetURL replacement is trivial on the JS side.
But that is not a proper solution for the ExtendScripter. A bit of extra work is needed.
Communicating between ES and JS
The ES and JS environments are quite separate, and run independently of one another. They don’t have a direct connection.
Instead, there is a ‘callback’-based interface. From the JS environment, one can initiate some operation in the ES environment. When the ES environment completes the task, it calls back to the JS environment with the results.
To compound the issue, the JS environment provides multiple independent runtimes (i.e. you can run multiple extensions concurrently), whereas the ES environment is a single-tasking shared resource: when an operation is launched from any of the active JS environments in ES, the ES environment gets busy in mumble mode, and any subsequent ES operation request from any other JS will stall until the previous ES operation is finished.
Lots more info about all this can be found here:
CSInterface
One thing that always annoyed me a bit was the asymmetrical nature of the interface between JS and ES.
In JS you have a ‘CSInterface’ thingy and you can call sumtin’ like
csInterface.evalScript(<some script>, <some callback>)
to execute a script in ES, and be woken by the callback when it is done.
JSInterface: Evening The Score
To even the score a bit, I first set out to create a similar ‘JSInterface’, as a component of my ‘GetURL.jsx’-replacement.
JSInterface allows you to call JS from ES. You can receive a callback when it is done.
The extension includes a CSXS manifest file that covers CC 2015 and CC 2017. I suspect it might run with lower versions after a bit of tweaking in the manifest, but I have not tried that.
For this to work, you also need to run your ES script in a persistent engine.
If you were to run in the ‘main’ (default) engine, the callback would not work because the whole engine vanishes out of existence as soon as the JS call returns and the ES script can run to completion.
Most of the time, a JS call will return before it completes, and a non-persistent ES engine will have gone by the time the JS call wants to perform a callback.
The main idea behind JSInterface is to allow developers to be ‘ExtendScript’-centric – i.e. concentrate on ExtendScript and ignore most of the JS details.
Often, you want to write some simple ExtendScript code, and don’t have any need for all the bells and whistles JS offers… except for one or two small features.
That’s where JSInterface comes in: you write all your code in ES, and occasionally call out to JS when you need something special.
JSInterface has a plug-in architecture. It currently comes with a single plugin, called ‘getURLPlugin’ which implements the coveted getURL method that can handle HTTPS.
Additional plug-ins can be added as needed: the idea is that you write little functions using JS/Node.js and expose them to ES via the plug-in architecture. You can then easily call them from ES, and JSInterface takes care of the glue.
Usage
Note: all material as-is: there is not much documentation. Use at your own risk.
You have a few options:
- Simple Installer:
https://www.rorohiko.com/downloads/ExtendExtendScript.1.0.4.zip
- If you want to delve a bit deeper: ZXP file and source code:
https://www.rorohiko.com/downloads/com.rorohiko.extendExtendScript.zip
This source code folder contains
– a subfolder call ‘dist’ containing a ZXP file
– a subfolder called ‘com.rorohiko.extendExtendScript’ with all source code - Run the installer, or install the com.rorohiko.extendExtendScript.zxp, or install manually.
For installing the ZXP file, you use something like Anastasiy’s Extension Manager (http://install.anastasiy.com/), or ZXPInstaller (http://zxpinstaller.com).
You can also set the CEP debug flag, then copy the ExtendExtendScript extension manually from the source code folder into one of the Adobe extensions folders.
Restart InDesign for good measure. - To see it in action: grab the following three files from the
com.rorohiko.extendExtendScript/examplefolder
and put them in the InDesign scripts folder or a subfolder thereof:
CallGetURL.jsx json2.jsx JSInterface.jsx
CallGetURL.jsx is the actual sample code; the other two files are modules that CallGetURL.jsx depends on.
- Double-click CallGetURL.jsx. A dialog with some data retrieved from an HTTPS-based URL should display.
JSInterface
If you want to delve into JSInterface or add plug-ins or suchlike, you’ll need to read a bit of code. Not much documentation there, but the code should be more or less straightforward.
Errata and Notes
Note 16-Dec-2019: There is now an easy installer for ExtendExtendScript which supports macOS Catalina and InDesign 2020.
Note 13-Mar-2018: There is now an easy installer for ExtendExtendScript. See https://coppieters.nz/?p=220
Note 11-Oct-2017: there was a bug in node_modules/GetURL/index.js. Around line 105 a line is missing:
protocol.get(parsedURL, function(response) { protocol.get(parsedURL, function(response) { response.setEncoding('binary'); // <<< THIS IS MISSING function checkForCompletion() {
I’ve updated the downloadable .zip file
I’ll be at the Creative Developers Summit in Atlanta in May 2017 – anyone in need of more info or assistance, feel free to catch up with me there!
Things To Come
Silly thoughts: I want to do some debugging/monitoring support in the panel (e.g. allow console.log from ExtendScript). Maybe get started on a ES Toolkit replacement or ScriptUI replacement as a panel?
Thank you so much for creating and sharing. This script is nearly perfect for our workflow! We’re currently using it to pull images from our Digital Asset Management system and place them right within our design documents.
Can you advise on an issue we’re having? When we crop an image (within the InDesign bounding box) and refresh the web version and re-run the script, it resizes the image to fit the bounding box. Is there a way to stop that. (So it keeps the format/cropping as it was – without having to resize and reposition each time?)
Thanks!
It is possible to do this in a number of ways. The simplest solution I can think of is to not change the image in InDesign, but instead only refresh the link. I.e. make sure the updated image file has the exact same file name and location (so InDesign sees it as an updated link). Then make your script refresh the image links and not touch the image frame or the placed image.