Updated BridgeTalk usage example with xbytor's sendSynch prototype

Adobe Community Professional ,
Apr 30, 2021 Apr 30, 2021

Copy link to clipboard

Copied

I have been messing with BridgeTalk lately and came upon a situation where my onResult techniques were really too cumbersome to do what I wanted in a succinct way: batch process something that repeatedly does stuff in Photoshop and then goes back to Illustrator.

My technique for BT caters to my desire of being able to send a single file to a customer via email, and it would have every thing it needs included embedded images and all of the necessary code. The way I send a BT message is:

  1. First always have a wrapper for my parent script that is a function with the name of the script.
  2. Have an argument parameter in the script's wrapper function declaration.
  3. But have the call to the wrapper function at the bottom and leave the aguments field empty.
  4. Have a statement before the procedural blocks that checks for (typeof(argObj) != "undefined") and does a JSON.parse or eval() on the argument to try convert from a string to an object. This object would have a key that gets checked by the checking block and has the super-important job of branching the execution into an appropriate sub-routine.
  5. When the cross-app portion comes up, send the entire script to BT by taking the script's wrapper function by name and converting it to a string with .toString(), then replacing the "()" execution parentheses with a stringified object that contains script-execution arguments. This way all of the shared functions are available for use in the sent script, such as all the polyfills to bring in new-er javascript features in. Again, I do this so when a customer runs these scripts, they can't get confused by a folder full of different scripts.

So this all used to work and works fine now, but it makes certain things more cumbersome such as when you want to keep a running log of a batch process.

The one problem with BrigeTalk.send() is that it is asynchronous and if you have a loop that pushes all results via an onResult function to a log, the calling function would return right away and be already done by the time the Photoshop part comes into play, returning an empty log.

Ok, well that's fine, just pass the log too, and keep passing this 'snowball' object back and forth. But what if I have a 'smart' log that has methods like a custom "toJSON()" method that edits the object for sending to other places? Maybe we can use toSource() on it and it would preserve the methods, I don't know, but what I do know is I don't want to be passing around objects which intuitively are meant to stay put in one spot and just get updated.

Now it was time to check out the BridgeTalk docs and the .pump() method, and I finally happened upon xbytor's famous code that motion graphics and Photoshop pros have been using for years and years: https://www.ps-scripts.com/viewtopic.php?t=11379

So I applied it and came up with the test script below, it appears to solve a lot of my issues.

  • First, it seems to negate the requirement to have #targetengine specified to work.
  • Second, it solves the issue of losing the caller script's app DOM when using an onResult function. In Illustrator if you use an onResult function to continue work in Illustrator, you'd have to actually send a new BT message this time to "illustrator" (although you are already there, that's where you started from) so that Illustrator application DOM is available. Now there's no need to send a message from Illustrator to Illustrator!
  • Third and also very important to me, the sync method allows for updating objects in-place as usual inside of the parent script.

Notes:

The following script has a variable 'log' that gets items added to it. This variable stays in its easily-managed spot and isn't passed around among BridgeTalk messages, which is just great.

eval() & toSource() vs JSON: the cool thing about toSource() is that it can preserve some things that JSON crashes with. But there's the concern for security: an object that may have been tampered with in some obscure way could be injected into the eval() statement to cause malicious deeds. Therefore I think now that using toSource and eval is ok when passing around objects you've made or native items, but when there's a UI or external data from somewhere it's better to have JSON.parse().

Even without a UI or external data, I'm not sure if the following hacks would still make toSource less-secure:

  1. Can someone write a new prototype or overwrite a property/method of a native object to have an eval() statement misbehave?
  2. Can someone write a very bad string combination into say, a pageItem.notes so that even though it's a string, the eval() statement can get tripped up due to obscure characters and treat it in the end as a piece of code rather than a string?

 

Anyway, supposing they aren't an issue, here is the snippet:

 

#target illustrator
// no #targetengine required? The sendSynch prototype seems to take care of it.

function test (argsObjAsSource) {
	/**
	 * @typedef LaunchScriptCategories
	 *  {"PsItemProcess"} PS_ITEM_PROCESS
	 */

	/**
	 * @typedef LaunchScriptArguments
	 *  {LaunchScriptCategories[keyof LaunchScriptCategories]} launchScript
	 *  {object} data
	 */
	
	// XBYTOR's code.
	// Send a synchronous message. The result is returned.
	// If a result doesn't come back in 'timeout' seconds, undefined is returned.
	BridgeTalk.prototype.sendSynch = function (timeout) {
		var self = this;
		self.onResult = function (res) {
			this.result = res.body;
			this.complete = true;
		}
		self.complete = false;

		self.send();

		if (timeout) {
			for (var i = 0; i < timeout; i++) {
				BridgeTalk.pump();       // process any outstanding messages
				if (!self.complete) {
					$.sleep(1000);
				} else {
					break;
				}
			}
		}

		var res = self.result;
		self.result = self.complete = self.onResult = undefined;
		return res;
	};

	function bridgeTalkEncode(txt) {
		/* thanks to Bob Stucky */
		txt = encodeURIComponent(txt);
		txt = txt.replace(/\r/, "%0d");
		txt = txt.replace(/\n/, "%0a");
		txt = txt.replace(/\\/, "%5c");
		txt = txt.replace(/'/g, "%27");
		return txt.replace(/"/g, "%22");
	};
	
	/**
	 *  {"photoshop" | "illustrator"} targetApp
	 *  {Function} scriptCode
	 *  {LaunchScriptArguments} args
	 *  {number} [timeout] - Number of seconds.
	 */
	function sendSyncBT (targetApp, scriptCode, args, timeout) {
		if (timeout == undefined) {
			timeout = 500;
		}
		var bt = new BridgeTalk();
		bt.target = targetApp;
		var argStr = "(" + args.toSource() + ")";
	
		var btMsg = "var scp = '" + bridgeTalkEncode(scriptCode.toString() + "\n" + scriptCode.name + argStr) + "';\n";
		btMsg += "var scpDecoded = decodeURI( scp );\n"; 
		btMsg += "res = eval( scpDecoded );";
	
		bt.body = btMsg;

		return bt.sendSynch(timeout);
	};

// ================================================= Script-Scope variables ================================================= //

	var batchInstructions = [
		{ prop1 : "A", prop2 : 0 },
		{ prop1 : "B", prop2 : 1 },
		{ prop1 : "C", prop2 : 2 },
	];
	var log = [];

// ==================================================== Script Functions ==================================================== //

	function psItemProcess (instrObj) {
		alert("Prop 1 is:\n" + instrObj.prop1);
		return instrObj.prop2;
	};

	function batchProcess (batchInstructions) {
		var thisItem;
		var launchInstructions;
		var thisResult;
		for (var i = 0; i < batchInstructions.length; i++) {
			thisItem = batchInstructions[i];
			/**  {LaunchScriptArguments} */
			launchInstructions = ({
				"launchScript" : "PsItemProcess",
				"data" : thisItem,
			});
			thisResult = sendSyncBT("photoshop", test, launchInstructions);
			log.push(thisResult);
		}
		return log;
	};

// ====================================================== Script Entry ====================================================== //

	if (typeof (argsObjAsSource) != "undefined") {
    /**  {LaunchScriptArguments} */
    var argsObj;
    try {    
      argsObj = eval(argsObjAsSource);
    } catch (e) {
      alert(e);
      return;
    }
    if (argsObj.launchScript == "PsItemProcess") {
      /* [Ps process] this entire script has been passed into Photoshop, and this argument decides which part is executed
      (this helps with code re-use, even among common functions used by different applications) */
      return psItemProcess(argsObj.data);
      /* [/Ps process] this return will output the data kept whole via the sendSynch() prototype of BridgeTalk. */
    }
  } else {
		// This is the initial Illustrator entry portion, perform initial check here.
		if (app.documents.length == 0) {
			alert("No documents are open.");
			return;
		}

		// synchronous execution, maked BridgeTalk easy to use by fitting into various procedures and maintaining original data of calling script.
		var result = batchProcess(batchInstructions);
                BridgeTalk.bringToFront("illustrator"); // otherwise they have to manually click Illustrator in dock to bring it back as active application.
		alert(app.activeDocument.artboards[0].name); // Still have access to DOM - not so with regular BT-message and onResult().
		alert(result.toSource());
	}

};
test();

 

 

Since this works with alerts (wouldn't it be embarrassing if it turned out this only worked with alerts?), I think I'll use this technique in all my procedural scripts now. There's really no need to do async BT messages other than when creating a ScriptUI palette script or something that take a while to do but you still want them to move around in the Illustrator document or a ScriptUI modal dialog. In my case the dialogs are usually dismissed before a batch process starts and there's been no instance of where it was covenient, necessary or required to send a message with hopes of using onResult but let users still do things in the document - usually the document is being processed in some way and I don't need the users to be able to touch it in any way.

Ok, so this is an example of a sync BT message, courtesy of the famous xbytor who does or did a lot of very advanced Photoshop stuff. For all who have BT issues, this should be quite a treat!

TOPICS
Scripting

Views

110

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Adobe Community Professional ,
May 01, 2021 May 01, 2021

Copy link to clipboard

Copied

LATEST

I think I may have been onto something when saying it could have turned out to work only with alerts!

Here's what I found:

  1. You definitely do not want to put any BridgeTalk.bringToFront's inside your cross-app function: this causes indefinite unresponsive times for me, I must have force-quit 30 times!
  2. For things which aren't just an alert, ie Photoshop doing something super-long, running lots of actions or doing anything besides an alert, make sure to bringToFront Photoshop inside the parent script, before the bt call with the cross-app function.
  3. Use BridgeTalk.bringToFront Illustrator inside the parent script after the bt call to Photoshop.

So, before with onResult I had no problems but I hated my code and the concept of passing around objects that shouldn't really be dynamic like that. I was afraid that the whole thing wasn't working, all this after I made changes to my code and didn't commit them or back them up, and I also restarted the computer just in case, for sure erasing my previous progress forever. Luckily after trial & error ( my favorite thing ), I came to conclude that without bringing to front in specific cases, the timeouts somehow mis-align to put Illustrator in a state of deadlock: Photoshop really had to be brought to front for all the stuff to work. I noticed this at first due to my Photoshop process actually starting up when I force-quit Illustrator.
With onResult, none of this was an issue and you actually have more freedom to click about while the other app was busy. This new synch method locks down both applications, which is what I want, but it doesn't look pretty (Windows: Illustrator is grayed out and black boxes appear to cover all the UI panels, dock icon hover app image splits into multiple panel thumbnails, Photoshop is more normal and nothing much happens there except you can't click on anything which is fine) and it looks like your computer is suffering some critical error while all this is going on. Maybe I can stick in some progress bar and incremental redraw() to make it look like it's not exploding.

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines