Injecting UXPScript Wrapper

Further to my previous post, it turns out the trick I discovered might be useful.

I am still on the warpath, in full ‘discovery’ mode, so it’s perfectly possible that the approach outlined below is moot and there are easier ways to do the same, but I am recording this for future reference nevertheless…

Part of what I do in CRDT_UXP is to handle a lot of stuff in a daemon process (e.g. there is a centralized logger). Sending stuff from UXPScript to the logs is async.

The issue is that the code in CRDT_UXP creates a lot of Promise-s without waiting for them to resolve.

I don’t want to slow down the code and wait for confirmation of, say, a crdtuxp.logNote() call.

So I will call
crdtuxp.logNote(...)
rather than
await crdtuxp.logNote(...)

Waiting would create an unnecessary game of ping-pong between the UXPScript code and the daemon process.

The problem arises when the UXPScript terminates: the wrapper code in InDesign that calls my script does not wait for any still-pending promises to resolve.

At the time of termination, any ‘loose’ unresolved promises are simply discarded, and my crdtuxp.logNote calls don’t even make it to the daemon.

So, with the trick in my previous post, I could:

1) cast away the async prefix to the wrapper, so the ‘wrapping function’ becomes a bog-standard JavaScript function.

To avoid confusing the calling code, I make this function return a Promise so the calling code needs be none the wiser and can treat the function as if it were an async function.

2) Before opening a new scope and create a new function I can replace the default Promise class with a proxy replacement which adds tracking code for any as-of-yet unresolved promises.

Because I am doing this ‘outside’ the scope of the wrapper function, I can ‘inject’ a new Promise class.

3) At the end of my script, I add

return Promise.all(Promise.pendingPromises());

which is a promise to end all promises.

The call Promise.pendingPromises() is a static method on my proxy Promise class which returns an array of all ‘not-yet-resolved’ promises.

As it turns out, the calling code in InDesign accepts this return value and (I assume) has an await on it.

This way, I can make sure the script fulfils any ‘dangling’ promises before terminating – so all my logging calls make it into the log files before the script terminates.

WARNING: the following code is unfinished ‘work-in-progress’ – don’t copy-paste this into your own project sight unseen!!

For reference, my .idjs script now looks as follows.

// Close off the async wrapper created by the calling context
});

... stuff ...

const inDesign = require("indesign");
const app = inDesign.app;

// Save the original Promise class
const SystemPromise = global.Promise;

let PROMISES_PENDING = {};
let LAST_PROMISE_UNIQUE_ID = 0;

// Define the new Promise class
class Promise {
    constructor(executor) {

        this._state = 'pending';
        this._value = undefined;
        this._id = ++LAST_PROMISE_UNIQUE_ID;

        // Create a new instance of the original Promise
        this._promise = new SystemPromise((resolve, reject) => {
            executor(
                value => {
                    this._state = 'resolved';
                    this._value = value;
                    if (this._id in PROMISES_PENDING) {
                    	delete PROMISES_PENDING[this._id];
                    }
                    resolve(value);
                },
                reason => {
                    this._state = 'rejected';
                    this._value = reason;
                    if (this._id in PROMISES_PENDING) {
                    	delete PROMISES_PENDING[this._id];
                    }
                    reject(reason);
                });
        });
        PROMISES_PENDING[this._id] = this;
    }

    // Proxy static methods
	static pendingPromises() {
		let retVal = [];
		for (let id in PROMISES_PENDING) {
			retVal.push(PROMISES_PENDING[id]);
		}
		return retVal;
	}
	
    static resolve(value) {
        return SystemPromise.resolve(value);
    }

    static reject(reason) {
        return SystemPromise.reject(reason);
    }

    static all(promises) {
        return SystemPromise.all(promises);
    }

    static race(promises) {
        return SystemPromise.race(promises);
    }

    // Proxy instance methods
    then(onFulfilled, onRejected) {
        return this._promise.then(onFulfilled, onRejected);
    }

    catch(onRejected) {
        return this._promise.catch(onRejected);
    }

    finally(onFinally) {
        return this._promise.finally(onFinally);
    }
    
    isPending() {
        return this.state === 'pending';
    }

    isResolved() {
        return this.state === 'resolved';
    }

    isRejected() {
        return this.state === 'rejected';
    }

    getValue() {
        return this.value;
    }    
}

... stuff ...

// Open a new, non-async wrapper to pass back to the calling context. 
// The closing '})' is not in this file - it is instead provided by 
// the calling context.

(function (exports, require, module, __filename, __dirname) {

... the meat of the code ...

// End with a promise which will finalize all pending promises
// so the caller does not exit until all is done

return Promise.all(Promise.pendingPromises());

// No }) here - the calling wrapper provides that!

UXPScript Tidbit

(note 2024-07-24: I’ve written a follow-up post, with a practical use of this technique:
https://coppieters.nz/?p=945)

I discovered this (probably useless) tidbit. When creating a UXPScript for InDesign, in its simplest form, you create an .idjs text file, and put a script into it.

You can then debug your script using Adobe’s Adobe UXP Developer Tools which are part of the Creative Cloud apps.

I noticed in the debugger, that when I ran my script, the calling context would wrap my script between two added lines:

(async function (exports, require, module, __filename, __dirname) {
.... the contents of my .idjs file...
});

which I assume is done by simple string manipulation, and then this string is parsed by the JavaScript engine, and then executed.

That led me to try a code injection in my .idjs script. I added a single line, at the top of my script.

This added line does not make sense on its own, but it ‘slots in’ when accounting for the wrapper.

After adding my ‘sneaky line’, my .idjs file becomes:

});(function (exports, require, module, __filename, __dirname) {
... the contents of my .idjs file ...

Hence, when I run this script, the effective code (i.e. my code combined with the wrapper provided by the calling UXPScript context) becomes:

(async function (exports, require, module, __filename, __dirname) {
});(function (exports, require, module, __filename, __dirname) {
... the contents of my .idjs file ...
});

My script does not contain any await statements, so it continues to work.

With this injection, I’ve ‘cast away’ the async nature of the wrapper, by replacing the async wrapper with a normal function wrapper.

I doubt that this is useful, and I don’t yet ‘grok’ the implications yet, but I suspect this might come in handy later, for example to inject stuff into the global variable space.

Things like:

(async function (exports, require, module, __filename, __dirname) {
});
let globalVar = 123;
(async function (exports, require, module, __filename, __dirname) {
… the contents of my .idjs file …
});

which creates a variable in the Script scope.

The reason I scratching around here is that I am currently exploring the ins and outs of UXPScript for InDesign, figuring out how to best integrate UXPScript into the Creative Developer Tools/Tightener technology stack and figure out how I can reduce friction and make the environment easier to use for simple ad-hoc scripts, which are the bread and butter of many workflows.

At present, I am planning to implement a ScriptUI-inspired UI environment for UXPScript, opening the door for simple UI, without needing to go full UXP. More later…

Flexible, configurable InDesign scripts with minimal effort

Preamble

When building InDesign scripts for creative users, the core functionality of the script often requires only a fraction of the development effort.

Most of the budget is needed for creating a user interface, such as a dialog or a panel, to allow the user to configure the script functionality.

I’ve recently started using a new pragmatic approach with an absolutely minimal user interface, often greatly reducing the necessary budget.

This approach also offers significant additional benefits beyond reducing development effort and cost.

The idea is simple: add a non-printing text frame somewhere in the document, typically on the pasteboard near the first page. This text frame contains configuration info for the script, using an enhanced version of the old Windows INI format.

This format, while more primitive than JSON or XML, is robust and human-readable, avoiding the pitfalls of complex delimiters and balancing issues. INI’s simplicity makes it resistant to corruption yet easy to use.

In an ideal world with unlimited budgets, one would alway create a supporting User Interface. In the Real World, using INI on the pasteboard is often Good Enough.

I am in the process of adding a supporting API into Creative Developer Tools; I’ll soon have the same API available both in CRDT_ES and CRDT_UXP. There are functions for parsing INI data, and post-processing values (e.g. when a boolean is expected, accepting input values like true, 1, yes, t, Y, Yes… as synonyms for true).

More info about CRDT here:
https://www.rorohiko.com/wordpress/creative-developer-tools/

INI – a quick refresher

INI data is line-oriented and consists of a mix of section lines and name/value lines:

A section line contains a string between square brackets, e.g., [Section1].

A name/value line contains a string followed by an equal sign and another string, e.g., firstName = John.

Example:

[Section1]
firstName = John
lastName = Doe
kids = Jill, Jack, Jeremy

Spaces around the equal sign are ignored. If a value string needs leading or trailing spaces, enclose it in double quotes:

nameWithExtraSpace = "    John Doe   "

Quotes around strings are ignored, allowing embedded quotes without escaping.

This is much easier to explain to end-users than the JavaScript/JSON escape \ mechanism.

Example:

nextLine = "Jeff said: "how are you, Doris?" while walking to the counter"

Advantages of Using INI in a Text Frame for Configuration

The advantages of stashing the config info in a text frame on the pasteboard are:

User-editable and Embedded

The config data is part of the document, making it user-editable and user-inspectable.

Unlike side-car files, it cannot get detached from the document.

Using a text frame is also easier than embedded metadata like XMP, which requires a UI for editing.

Expresses Document Dependency on Automation

The presence of a config text frame indicates that the document is associated with some automation. Including comments in the INI frame can provide additional information about the script.

Human-readable and Comment-able

The data is human-readable and can include explanatory text. Anything that doesn’t look like INI data is ignored.

Instills Confidence for the End-User

The robust format minimizes accidental data corruption, giving users confidence when tweaking the config.

There are no hidden settings that can cause unexpected behavior.

Easy to Transport

The config data can be easily copied between documents or templates by copying the text frame.

No Need for a UI

For simple scripts, there is no need to create a CEP panel or ScriptUI interface. The INI config frame can be made self-explanatory.

Enhancements

Case-Insensitive and Space-Insensitive

The format is mostly case-insensitive and ignores most whitespace, except within value strings.

Characters in name strings are limited to a-z, A-Z, 0-9; any other character within a name is ignored.

Example: the following two files are equivalent:

[Section1]
firstName = John
lastName = Doe
kids = Paul, Joseph, Joanne
[ section 1 ]

first name = John

last name = Doe

K I D_S = Paul, Joseph, Joanne

Lenient Quoting

Different types of quotes are accepted without distinction, making the format robust even when using smart, unbalanced quotes. Curly quotes are just fine.

No Need to Teach Escaping \

Because the quote characters are not ‘special’ they can be embedded into value strings without need for the complexities that come with the JavaScript escape character.

Leading and Trailing Spaces in values

Leading and trailing spaces in value strings are ignored unless the value string is enclosed in quotes.

Examples:

name1 = John Doe
name2 =     John  Doe

the spaces within the values for name1 and name2 are taken into account. name1 is "John Doe" (1 space between first and last name) and name2 is "John Doe" (2 spaces).

If leading or trailing spaces are needed in a value string it can be enclosed in quotes.

name1 = “ John Doe ”

Arrays

By default, INI has no support for arrays. It remains up to the script developer to work around this.

One simple approach is to accept comma-separated strings in the value and use the split() function in JavaScript to split the value. This is often Good Enough for arrays of strings.

If more complex objects are needed, the enhanced INI has some rudimentary support for arrays of objects, by allowing the user to repeat a section name and each time the section name is repeated, it constitutes a new object entry in the array.

Re-using a section name is interpreted as ‘next row’.

For example:

[xyz]
a=1
b=9

[xyz]
a=2
b=76

[xyz]
a=556
b=4

Each time the [xyz] re-occurs, a row counter is incremented, and the row counter automatically suffixed to the names.

Initially, the row counter is 1 (first row). The first row is a special case, and for the first row, there is no suffix.

From the second row onwards, the suffix is _2, _3,…

The data above is equivalent to:

[xyz]
a=1
b=9
a_2=2
b_2=76
a_3=556
b_3=4

This can then be post-processed with the CRDT API into the following object:

[
    {
        "a": 1,
        "b": 9
    },
    {
        "a": 2,
        "b": 76
    },
    {
        "a": 556,
        "b": 4
    }   
]

Creative Developer Tools

The Creative Developer Tools runtime contains methods to parse and post-process enhanced INI data.

I won’t document them here – the source of truth is the documentation of CRDT_ES and CRDT_UXP. At the moment, I am still adding new methods to the API.

Networking Event – Creative Developers Summit, Washington DC

An event you need to note down in your calendar:

  • Creative Developers Summit (July 10 + 11, 2024, Washington DC, co-located with the CreativeProWeek)

Creative Developers Summit

Since 2010, I’ve helped organizing our independent Creative Developer Summit, co-located with CreativeProWeek.

The 2024 Creative Developer Summit happens on July 10 and 11, at the Crystal Gateway Marriott hotel in Washington, D.C.

Make sure to read this blog post of mine:

This independent developer get-together is organized by developers for developers. Come and join your tribe!

Speakers and Topics:

Date: July 11:

Justin Taylor (Hyper Brew): Bolt UXP: Build Adobe UXP Plugins Faster
Hamza Habeeb (Adobe): Adobe Express Add-ons: Spark Creativity with Powerful Gems
Harbs (Santa Cruz Software): First-hand Experience Building Express Add-ons
Kris Coppieters (Rorohiko): Creative Developer Tools: Realizing the Tightener Technology Stack, Three Years On
Caleb Clauset (Typefi): Typefi’s Dev Tools
Colin Flashman (colecandoo): Esko Without a Budget
Keith Gilbert (Gilbert Consulting): From the Trenches
James Lockman (Adobe): A First Look at the Adobe Firefly and Creative Cloud APIs
Erin Finnegan (Adobe): Adobe Creative Cloud Developer Champion Program
Adobe Reception: For Community Experts and Developers

Date: July 10

Workshop: Integrating Firefly with Creative Cloud APIs
Instructor: James Lockman and Adobe Team

* In person registration:

To attend the summit in-person, purchase a 1- or 2-day CreativeProWeek ticket.

https://creativeproweek.com/dc-2024/creative-developers-summit/

And don’t miss the networking opportunities at the hotel bar!

Fees cover food, drinks, conference room access, and Wi-Fi. Message or email me (kris at rorohiko.com) for a special developer discount code to get 50% off.

* Remote attendance is free. Register here:

https://bit.ly/creativedev2024

PluginInstaller, or ‘getting there’.

It’s been a long, hard slog! Nearly three years ago, I set out to build a technology stack to address the gaps in Adobe’s automation tooling for the Creative Cloud ecosystem.

It seemed like a crazy endeavor for one person, but persistence (and a lot of blood, sweat, and tears) paid off.

I’ve finally reached the ‘minimum viable product’ stage, where I can use and present some of the tools I’ve been working on. There’s still much more to do, but the first tools are now available for use.

I’ve started converting Rorohiko’s automation tools to this new technology stack (currently TextExporter, JSXGetURL, SmokeWordStacks, SizeLabels)

The project, originally called Tightener, has stabilized over the last 6-8 months, and I’m now focusing on building tools for automating Adobe Creative Cloud apps (InDesign, Illustrator, Photoshop, etc.).

This subset of the project is called Creative Developer Tools, with two main components: CRDT_ES (for ExtendScript/CEP) and CRDT_UXP (for UXPScript/UXP).

Another key component built on top of Tightener and CRDT is PluginInstaller.

PluginInstaller aims to be a unified packager and installer for various types of software, including ExtendScript, CEP panels and soon UXPScript, and UXP panels.

It also uses a central registry to track users, machines, activations, and usage without storing personal data. The registry uses SHA-256 hashes, keeping the actual data secure on users’ computers.

Future features

Stuff I have in mind, which might or might be realized:

  • Porting the Rorohiko scripts, extensions and plugins to use PluginInstaller
  • PluginInstaller support for C++ plugins and hybrids
  • PluginInstaller support for UXPScript
  • PluginInstaller support for UXP Plugins
  • CRDT module with a look-similar feature for ScriptUI that can be used with both ExtendScript and UXPScript
  • CRDT module for user-interface development, to replace CEP and UXP for creating user interfaces
  • PluginInstaller search feature in various catalogs/enhancement store
  • Jupyter Notebook for ExtendScript
  • Python for automating Adobe Creative Cloud

Currently Available

Here’s a look at the currently implemented elements of the technology stack, from outer to inner components.

PluginInstaller

  • Generic Platform: Initially designed for the Adobe ecosystem but adaptable to other environments (e.g. Python ecosystem).

  • Unified Package Format (.tpkg):

    • Text-based format with metadata in the first 1KB, ensuring easy identification.
    • Capable of packaging and installing ExtendScript and CEP solutions. UXP soon.
  • Comprehensive Tool Replacement:

    • JSXBIN and ZXPSignCmd Replacement: Manages self-signed certificate generation and .zxp file creation.
    • ExtensionManager Replacement: Installs .tpkg files directly from URLs or after download.
  • Advanced Features:

    • Facilitates monetization and purchases, with a proof-of-concept integration with PayPal used in our Rorohiko products.
    • Payment processor agnostic. Developers can slot in their own payment processor if desired.
    • Handles activations and selective feature enablement for monetization.
    • Supports demo/trial versions and tracks anonymized usage data, providing insights into tool installations and usage frequency.
    • It’s now easy to experiment with different payment models: donationware, nagware, short subscriptions, long subscriptions…
    • Unified install experience for the end-user
    • Open for use by other developers
    • Pricing is still up in the air, but my intention is to make it totally free for small developers (1- or 2-man bands) with a revenue below a certain level.

Creative Developer Tools

  • CRDT_ES:

    • #include-able library for ExtendScript/CEP
    • Provides useful functions and acts as a JSXBIN replacement.
    • Enables code-signing for ExtendScript.
  • CRDT_UXP:

    • require()-able library for UXP/UXPScript
    • Offers functions that extend beyond the security sandbox.
    • Provides missing features such as environment access, binary file read/write capabilities, and encryption.
  • JSXGetURL:

    • Enhances ExtendScript with CURL features, effectively integrating Tightener capabilities.

Tightener Registry-Enabled Version

  • Dependencies:
    • In addition to Tightener Base also needs OpenSSL, curl, and zlib, to allow communication with the registry.
  • Advanced Features:
    • Unique Identifiers: Generates unique machine, user, and order GUIDs.
    • Central Registry: Uses a registry that stores only hashed data, ensuring no identifiable information or passwords are kept.
    • Distributed Data Storage: Relies on remote nodes (i.e. the user’s computer) for data storage needs.
    • Capability Tracking: Supports activating and managing floating licenses and activations.
    • Persistent Data Handling: Manages persistent local usage data for demo modes, trial versions, etc.
    • License Management: Handles floating licenses, allowing large accounts to allocate seats dynamically without requiring PluginInstaller for each end-user.
  • Availability:
    • Offered in various formats: as a command-line tool, or compiled and statically linked into various apps, as a DLL, or as a plugin for different environments.

Tightener Base Version

  • Core Features:
    • Pure C++ Code: Built entirely in C++ with no dependencies beyond the C++11 standard library.
    • Embedded Glue Language (TQL): Used for bridging; can also provide a scripting DOM into applications, akin to ExtendScript.
    • Messaging Protocol: Facilitates inter-app communication using a star topology, similar to Adobe Bridge’s protocol.
    • Optional Networking: Through gateway apps, multiple computers can be connected into a mesh of stars, enabling cross-network messaging.
  • Availability:
    • Offered in various formats: as a command-line tool, or compiled and statically linked into various apps, as a DLL, or as a plugin for different environments.

CEP: In-Circuit Debugging of a .zxp File

It’s not common knowledge, but you can package a CEP extension into a .zxp file and debug it ‘in-circuit’, without needing to set the CEP debug flag.

My trick is to not use the ZXPSignCmd to package up the .zxp file.

Last time I checked, as far as I could tell, the ZXPSignCmd app takes notice, and will omit the .debug file and will refuse to add it.

However, my new PluginInstaller is able to inject a .debug file into a packaged .zxp.

As it so turns out, that makes the installed extension debuggable, without setting the CEP debug flag.

You would not want to do this in a release version of your extension – but when it comes to diagnosing and debugging difficult-to-catch bugs on someone else’s workstation, this can be invaluable.

Below a little cookbook to demonstrate how you can go about this.

I’ve also made a ‘live video’ about it. The quality of the video is what it is, I think you can tell I am not interested in becoming a ‘Youtuber’. My main concern is the content, not the presentation.

https://youtu.be/rVPXdtRMx6Y

First, download the CEPSparker project.

https://github.com/zwettemaan/CEPSparker

Click the green Code button and then click the option Download ZIP.

CEPSparker is a framework I made to help me generate ‘starter’ projects for CEP.

Important to note: CEPSparker is not a CEP extension project. It is a precursor to a CEP extension project.

`After downloading, if you’re on a Mac, you MUST right-click the initialSetupConfigApp.command file, select Open, then allow it to run by clicking the Open button.

This de-quarantines the other .command files so they can be double-clicked.

On Windows, expect some blue warning screens (click More Info and run anyway).

You will need to run the .bat files from the command line in an elevated Administrator CMD session; you can double-click sudo.bat to start such a session.

Next, double-click the SparkerConfig or SparkerConfig.exe application. For the demo, change the STARTERCODE dropdown menu and pick IFrameUIServer.

Click Generate.

This will generate a sample CEP project that has a ‘split personality’. The CEP panel is featureless, and only contains an <iframe>.

The content of this <iframe> is pulled from a web server.

The sample code demonstrates how such server-provided code can drive a user-interface inside a CEP panel, and interact with the local host app.

For the demo, we use a small local Node.js application to play the role of the ‘remote server’. The Generate will have created a lot of folders in the CEP project folder.

One of them is IFrameUIServer

Inside you find a little file server.js. You need to have Node.js installed; Google around if you don’t have it installed yet.

All you need to do is open a command line and navigate into this folder, then run

node server.js

and that will run a tiny web server on http://localhost:8001

Next, we will tweak the CRDT manifest for this CEP panel. The CRDT manifest (a file called CRDT_manifest.json) is used by PluginInstaller to determine what it needs to do to package the CEP panel.

Open CRDT_manifest.json in a text editor and change it so it reads:

...
"injectDebugFile": true,
...

You can leave the "productCode" entry set to CEPSparkerTest, for the sake of argument – we need this code for creating a product entry in PluginInstaller.

This tell the PluginInstaller to copy the debug file from the CEP Project into the ZXP and rename it to .debug.

If you inspect the debug file in a text editor, you see it configures the debugger to use port 8888:

<?xml version="1.0" encoding="UTF-8"?>
<ExtensionList>
    <Extension Id="com.rorohiko.cepsparker.samplepanel">
        <HostList>          
            <Host Name="IDSN" Port="8888" /> 
        </HostList>
    </Extension>
</ExtensionList>

Now run PluginInstaller and switch to developer mode (File – PluginInstaller ModePublisher/Developer). If you don’t have a registered account yet, you need to create one first: after switching to Developer mode, switch to the Accounts window.

Registration is free, but needs to be approved.

To register, you need to click New and fill in a terse bit of detail.

Once you click Register you’ll need to wait for a confirmation email, and after that, I will need to handle your registration request, and I will need to approve it first; if I don’t personally know you I’ll reach out via email.

Once you have a registered developer account, you can create a product entry in the Products window.

For this test, you can just fill in some random details. The only thing that needs to match is the product code which is CEPSparkerTest (unless you changed the "productCode" in CRDT_manifest.json – these names need to match for PluginInstaller to find the manifest).

Once you complete the new product entry, you can click Package… and navigate to the CEP project folder.

Once the packaging completes, you’ll find a .zxp file in the build subfolder.

For the demo, you can now install this .zxp file using a standard CEP installer (for example Anastasiy’s Extension Manager).

Make sure the Adobe CEP debug flag is turned off before trying this out if you want to verify that the debug flag has no influence on the debuggability of this .zxp

With the IFrameUIServer running, start InDesign and open the panel.

The panel should populate and show a UI that has a New Document button. This whole UI is served from the remote web server – if you look at your Node.js terminal window, you’ll see the http requests fly past.

Click New Document to create a new document.

Now start Chrome and access http://localhost:8888 – this will show you the familiar debug interface with two available sessions: one for the panel itself and a second one for the user interface inside the <iframe> on the panel.

Reach out to me if you find this kind of stuff helpful or interesting. I can help you kickstart your automation project and greatly reduce the time needed. In most of my projects, my role is to be a temporary member of the team, and help build the software, by managing, training, designing and coding, as needed.

https://www.linkedin.com/in/kristiaan

Creative Developer Tools – status update

I’ve been working hard on Creative Developer Tools (CRDT).

My current focus is to implement a unified packager/installer/license management for third party Adobe scripts/extensions/plug-ins.

PluginInstaller handles the whole developer-to-user chain: packaging, downloading, installing, removing, updating…

I’ve just finished adding support for CEP. PluginInstaller now handles plain ExtendScript as well as CEP extensions. Next up is UXP.

PluginInstaller is part of the growing suite of Creative Developer Tools.

Unified Packager/Installer

PluginInstaller handles both packaging (developer side) and installing (user side) of third party enhancements to the Adobe Creative Cloud apps.

I’ve just released a new version of the PluginInstaller which combines support for plain ExtendScript (.jsx) as well as CEP panels (.zxp).

My plan is to further extend PluginInstaller so the same packager/installer will also handle UXP/UXPScript and C++ plug-ins.

PluginInstaller uses a unified package format (.tpkg) which combines a number of features:

  • Code signing
  • Code encryption
  • Licensing and activation
  • JSON meta-information in the .tpkg file header
  • Support multiple types of solutions (.jsx, .zxp…)

PluginInstaller can be downloaded here:

https://tgrg.net

CEP: no more ZXPSignCmd

I’ve managed to fully integrate all the steps needed for .zxp code signing into CRDT/PluginInstaller.

As we all know, Adobe’s .zxp code signing is both required and ineffective – i.e. it’s just security theater. Just an additional hurdle that developers have to jump.

When packaging a CEP extension using PluginInstaller, it will automatically create and manage the necessary self-signed signing certificates during the packaging operation.

Separate of the Adobe code signing setup, CRDT/PluginInstaller implements a more generic code signing mechanism. This mechanism can handle all kind of project types, including plain ExtendScript script projects.

When PluginInstaller manages a CEP extension, it transparently manages two distinct ‘layers’ of code signing: one as imposed by Adobe, the second layer is handled by CRDT.

PluginInstaller will build both a standard .zxp as well as a .tpkg file when packaging a CEP extension.

The .zxp file is a courtesy file which is not needed nor used by CRDT. It allows the developer to opt for using existing installer tools (e.g. Anastasiy’s Extension Manager) instead of PluginInstaller.

Such .zxp is not protected by CRDT code signing, but it can still have access to the protective CRDT code encryption features as long as the CRDT_ES runtime is included into the package.

The PluginInstaller also handles downloading of .tpkg and handles management of activation files for commercial solutions that need licensing/demo configurations.

CRDT Encryption

CRDT uses two levels of encryption, for two different purposes: an outer level (public/private key) handles code signing, and an inner level (AES-256) handles code protection.

Code signing is there to protect the user: they can verify that the code they are about to run was created by someone they can trust.

Code encryption is there to protect the developer: it blocks malicious users from inspecting and copying the crucial code magic that makes a software solution work.

.tpkg files

.tpkg files are self-identifying. They are structured so they have a human/computer readable JSON header which contains useful meta-info about their content.

This also allows the use of partial downloads: we can download the first 1024 bytes of any .tpkg file and figure out what they are without having to download the whole file.

CRDT Code Signing

When a developer registers a developer account in PluginManager, it will use OpenSSL to automatically generate a public/private key pair and publish the public key in tgrg.net online registry. PluginInstaller handles key generation automatically and there is no cost to creating these keys.

The private key is only stored on the developer’s workstation and used to encrypt anything that gets packaged by the developer.

A package will only work when it is decrypted with the matching public developer key as retrieved from the registry.

The approach was chosen to avoid forcing developers to deal with expensive code-signing certificates.

When a developer creates a .tpkg, the file is encrypted using the developer’s private key.

Then, when the user downloads and installs the .tpkg, the file is decrypted using the developer’s public key as retrieved from the registry.

That way the user can make sure they know who the .tpkg was created by.

Code encryption

When a .tpkg file contains .jsx files, they are encrypted using AES-256.

CRDT_ES comes with a binary runtime in a .dylib/.dll. This runtime is loaded into the ExtendScript engine inside the host app, and it handles on-the-fly decryption and execution. This works for plain .jsx as well as for .jsx embedded inside a CEP solution/.zxp.

CRDT currently does not transparently handle encryption of other file types than .jsx (e.g. .js).

Encryption of .js files (as opposed to .jsx files) would currently not be a strong deterrent for hackers because the decrypted .js code could be inspected by way of a debugger, simply by putting a breakpoint after the decryption call.

.jsx is different: CRDT can execute the encrypted code straight from the CRDT runtime, which is binary C++ code, without making the code easily inspectable in an ExtendScript debugger. By judiciously using closures, the code can be made inaccessible to would-be snoopers that know how to use the ExtendScript debugger.

For .js I cannot do that (yet). I do plan to change this later, but I need to do some research and find Adobe documentation: I need to be able to embed and call a binary runtime from the embedded Node.js or UXP engines. I am eagerly awaiting the ability to call C++ code from a UXP script.

For the time being, if you have .js code that needs to be protected from prying eyes, stash it in a .jsx and call it from .js through CSInterface, or else use the ‘standard’ approach of uglifying the code.

Licensing/Activation/Demo

CRDT encryption/decryption is also tied into the tracking/licensing/activation mechanisms. It allows developers to add code to decide whether their plugin/script is active/not active/in demo/paid for mode…

Depending on the situation, their extension can stop working, or be limited in features – whatever they want.

Payment Handling

Currently I don’t have payment handling integrated yet; but I plan to build sample integrations for PayPal and Stripe.

Developers can then decide to either use my integrations or create their own.

For the time being, when using CRDT for licensing, users need to pay the developer via a standard invoicing mechanism, after which can be emailed an activation file generated in PluginInstaller by the developer.

This is a manual process, which should be manageable for small developers.

Eventually this will all be fully automated.

CRDT also has support for sub-licensing, and can handle floating licenses to support licensing a developer’s solutions to larger companies.

Creative Developer Tools Progress

Creative Developer Tools (CRDT) is taking shape!

The ExtendScript version (CRDT_ES) is now quite functional and usable.

I’ve used one of my old ‘free’ ExtendScripts (SizeLabels) as a real-life test, and wrapped it up as a CRDT package, using a lot of the code-signing, encryption, licensing, trialware, installer… features of CRDT to make it into a ‘real’ project that could start generating some revenue.

A more in-depth developer perspective on how I used CRDT to repackage SizeLabels in this YouTube video:

https://youtu.be/rn9VQOEHDTM

CEP Extensions: Packaging/Code Signing/Encrypting in CRDT

The next subproject I’ll be tackling in CRDT are CEP extensions.

All the CRDT for ExtendScript features are already available in CEP extensions – so the monetization and other features offered by CRDT can already be integrated and used.

But there is more work to do.

The next goal is to reduce the friction related to code signing, packaging and encrypting CEP extensions

If all goes according to plan, PluginInstaller will handle both the packaging (by the developer) and installing (by the end-user) of CEP extensions.

It will transparently handle both the ‘mock’ code signing (CEP code signing) enforced by Adobe as part of its security theater, and will add a layer of ‘real’ code signing on top of that (CRDT code signing).

And of course, CRDT will provide a developer with the features they need to monetize their work.

CEP Code Signing – ucf.jar

PluginInstaller needs to handle the CEP code signing, so I’ve been doing some research, and I’ve discovered that the good old ucf.jar in the crusty old Adobe signingtoolkit can be made to work and can jpackage-d with a recent Java JDK.

I’ve got it working on my M2 Mac, and will now integrate it into PluginInstaller.

I am hopeful this will allow me to enhance PluginInstaller to transparently manage CEP code signing

PluginInstaller will then automatically create the self-signed key and sign your extension, making the creation of a package just a little bit easier.

CEP Extensions: Packaging/Code Signing/Encrypting

The aim is for PluginInstaller to transparently handle two types of code-signing, and a layer of encryption.

  • on one hand handle the Adobe-enforced CEP code signing
  • additionally, and independently, it will handle CRDT code signing, which depends on a registry of public keys to keep track of developers and their signatures.
  • it will also handle CRDT code encryption which is an AES-256-based encryption meant to replace JSXBIN.

CEP Code Signing

The whole bother with Adobe enforcing developers to sign CEP extensions is nothing but security theater. By making PluginInstaller handle this, I can remove another hurdle for CEP extension developers.

These signing keys are not verified in any which way by the Creative Cloud apps, so anyone can create their own key and sign anything and the Creative Cloud software will accept it, sight unseen.

Any hacker can claim to be anyone else, and create a self-signed key to ‘prove’ it to the Creative Cloud apps, and the Creative Cloud apps will happily take them at their word.

Nothing stops a malicious hacker from unzipping someone’s genuine .zxp, adding some malware to it, and re-package it with their own self-signed key.

CRDT Code Signing

CRDT code signing requires developers register their identity in a registry, so the PluginInstaller can verify their signature.

When a developer creates a developer account in PluginInstaller, a public/private key pair is automatically generated and the public key is published in the registry.

Developers retain their own private key locally, and PluginInstaller will use the private key to code-sign their scripts and data files when it packages the software.

CRDT Code Encryption

Additionally, CRDT also implements CRDT code encryption. This allows developers to protect their code from prying eyes. This is a replacement for JSXBIN as provided by ExtendScript.

What’s ahead

There is still a lot of work to be done on CRDT. Important things that are on my to-do list:

  • integrate payment processors like PayPal and Stripe. At the moment, payments are still handled manually.
  • handle packaging and installing of UXP plugins the same way ExtendScript and CEP Extensions are handled, so PluginInstaller can be a one-stop shop both for packaging (developerland) and installing (userland)

Creative Developer Tools

The first public alpha versions of Creative Developer Tools for the Adobe eco-system are now becoming available.

UXP and UXPScript are great, but for software solutions that are running in a ‘clean room’, having to fight the UXP security sandboxes can make a developer’s life miserable.

Also, handling activation/licensing for commercial software can be a pain. Creative Developer Tools can handle that for you.

Creative Developer Tools for UXP currently works with Photoshop and InDesign and provides a growing set of APIs, including APIs for reading/writing binary files and text files without artificial limitations imposed by the UXP sandbox.

See

https://github.com/zwettemaan/CRDT_UXP/blob/main/docs.md

for more info.

The crdtuxp module contains a mix of synchronous and asynchronous functions.

Some functionality is written in JavaScript as part of crdtuxp.js, and is synchronous.

Other functions are delegated to a daemon process which is able to operate outside of the UXP sandbox, but still within the confines of the logged-in user. These functions are always asynchronous.

Important note: crdtuxp steps out of the UXP security sandbox – which means that, as a developer, you need to be judicious how and when to use this.

The reality is that every software system operates in a unique context.

UXP security measures have their place and can be helpful in keeping things secure when it comes to UXP development.

However, for many software systems, these measures are too strict and overbearing and don’t account for the actual operating environment. It’s an all-or-nothing approach.

In my opinion, it should be up to the user/developer/IT department to decide how to handle security.

Sometimes a particular workflow system will be living inside a walled garden, on a disconnected network, without any contact with the outside world and will not be allowed to run any unvetted software. Or othertimes the OS security is safe enough for the particular workflow at hand.

In those cases, the UXP security measures are counter-productive: they represent unnecessary hurdles to the software development, and often make the user interface clunky and user-unfriendly.

Using UXP sandboxing should be a developer-selectable option, not an enforced ‘always-on’ requirement, and it should be up to the developer and/or the IT department to decide what is appropriate and what not.

Creative Developer Tools puts the power back into the hands of the developer, the end-user and the I.T. department, and leaves it up to them to opt-in or opt-out of the UXP security sandbox.

https://www.rorohiko.com/crdt

JSXGetURL now live

JSXGetURL enhances ExtendScript to make it easy to access servers using httphttps, sftp… – e.g. to download assets from a remote server, straight from ExtendScript. It supports InDesign, Illustrator, Photoshop,… – any Adobe software with ExtendScript support.

There is nothing to install – all you need to do is add a single //@include line to your script.

I’ve further enhanced the License Manager, and JSXGetURL 1.0.4 is now live on the Rorohiko web site.

I’ve added a feature that allows ‘sublicensing’, where you can embed an activation code for JSXGetURL into your own solution, so your end-user does not have to deal with activations.

Have a look at

https://www.rorohiko.com/jsxgeturl