×

Omni Automation Script URLs

Updates to the suite of Omni applications include changes to the security architecture regarding the execution of external Omni Automation scripts via URLs, such as those generated and run by 3rd-party applications and webpages. Specifically, the ability to use an application preference to control the security interaction with external scripts has been discontinued. This change is reflected in the following application release note:

Remote Script Invocation — Replaced the OJSBypassPreviews preference with support for pre-approving individual scripts. Remote scripts are also disabled by default now, with an interface in the Omni Automation configuration area to enable them.

The following pages (4) detail the new security procedures and specify how to construct remote script URLs to enable them to be used repeatedly upon an initial user-approval process.

NOTE: The new security protocols do not effect Omni Automation plug-ins installed by users.

Background

Omni Automation is based upon the JavaScriptCore frameworks that are included with all Apple devices and platforms (iOS, iPadOS, macOS). The JavaScript scripting language component of these frameworks is an essential element in the construction of the webpages that make up the World-Wide-Web, and is also commonly used to automate the processes of many 3rd-party applications.

Because of this shared JavaScript “heritage,” 3rd-party applications and webpages are able to create and execute Omni Automation script URLs containing JavaScript script code and passed data, enabling direct interaction with the full suite of Omni applications.

These “external” scripts may be static (the same code executing the same each time) or they may incorporate a design that enables data to be processed dynamically by the targeted Omni application, with the processed data changing each time the script is run.

Regardless of the script’s design, Omni Automation scripts are “sent” to the targeted Omni application via URLs that contain the encoded Omni Automation script code, and optionally any unique data to processed by the script.

This section of the website details how to use standard JavaScript code in apps and webpages to create encoded Omni Automation script URLs.

The Script URL Syntax

Omni Automation script URLs have a simple design and implementation, that includes the delivery and execution of customized Omni Automation scripts, without requiring the complex chains of parameter=value pairs commonly used in other URL automation scenarios.

Omni Automation script URLs incorporate two parameters, the second of which is optional:

Here is the Omni Automation script URL format, beginning with the lowercase name of the targeted Omni application:

[Omni app name]://localhost/omnijs-run?script=[%-encoded script]&arg=[%-encoded argument]

Script Design

For external scripts that include the processing script code as well as the data to be processed by the script, there are essentially two designs that work well:

Script as Statement(s)


argument.forEach(arg => {console.log(arg)})
Script as Function


(function logTheseItems(args){ args.forEach(arg => {console.log(arg)}) })(argument)

NOTE: The optional script argument can be provided as a string, array, or object.

Argument (optional)


// string argument "How now brown cow." // array argument ["East", "South", "North", "West"] // object argument {"name":"My New Project", "notification":-3600, "dueDate":"Mon Apr 19 2021 09:30:00 GMT-0700 (PDT)"}

URL from Script Statement(s)

The following example demonstrates how to use JavaScript to create an Omni Automation script URL from a script and data.

In this simple example, the Omni Automation script will iterate an array of passed strings, logging each to the automation console in the target Omni application.

NOTE: prior to executing the example script below, you will want to become familiar with the built-in Omni Automation script security protocols.

omnifocus://localhost/omnijs-run?script=argument.forEach(arg%20%3D%3E%20%7Bconsole.log(arg)%7D)&arg=%5B%22A%22%2C%22B%22%2C%22C%22%5D
omnigraffle://localhost/omnijs-run?script=argument.forEach(arg%20%3D%3E%20%7Bconsole.log(arg)%7D)&arg=%5B%22A%22%2C%22B%22%2C%22C%22%5D
omnioutliner://localhost/omnijs-run?script=argument.forEach(arg%20%3D%3E%20%7Bconsole.log(arg)%7D)&arg=%5B%22A%22%2C%22B%22%2C%22C%22%5D
omniplan://localhost/omnijs-run?script=argument.forEach(arg%20%3D%3E%20%7Bconsole.log(arg)%7D)&arg=%5B%22A%22%2C%22B%22%2C%22C%22%5D
Script with Passed Input
    

scriptString = `argument.forEach(arg => {console.log(arg)})` scriptInput = ["A","B","C"] encodedScript = encodeURIComponent(scriptString) inputString = JSON.stringify(scriptInput) encodedInput = encodeURIComponent(inputString) targetAppName = "OmniFocus".toLowerCase() urlString = `${targetAppName}://localhost/omnijs-run?script=${encodedScript}&arg=${encodedInput}`

 1  The script (as a string) to be executed by the targeted application. Note that the script is encased in tick marks (`script goes here`) to convert the JavaScript code into a string for encoding and inclusion in a URL. IMPORTANT: note the use of the term “argument” that is used by Omni Automation as a special placeholder for the data that will be passed into the script when it is executed. Avoid using the term “argument” elsewhere in your script as a variable name.

 2  The data to be passed into the script, replacing the term argument. In this example, the passed data is an array of strings, each of which will be logged to the application’s scripting console.

 4  In preparation for inclusion in a script URL, the script string is percent-encoded using the JavaScript encodeURIComponent() function.

 5  The input data (in this case the array of strings) is converted to a string using the stringify() method of the JSON class.

 6  In preparation for inclusion in a script URL, the input data string is percent-encoded using the JavaScript encodeURIComponent() function.

 8  The lowercase name of the Omni application that will execute the passed script and input data.

 9  The script URL string is created using text placeholders ${variableName} placed with the string. Note that this Omni Automation script URL takes two parameters: script= followed by the encoded script, and &arg= followed by the encoded argument data.

For this example, the resulting script URL looks similar to:

Encoded Script URL


omnifocus://localhost/omnijs-run?script=argument.forEach(arg%20%3D%3E%20%7Bconsole.log(arg)%7D)&arg=%5B%22A%22%2C%22B%22%2C%22C%22%5D

URL from Script Function

The following example demonstrates how to use JavaScript to create an Omni Automation script URL when the source script is written as a single function that accepts and processes passed-in data.

NOTE: prior to executing the example script below, you will want to become familiar with the built-in Omni Automation script security protocols.

omnifocus://localhost/omnijs-run?script=%28function%20logTheseItems(args)%7B%0A%09args.forEach(arg%20%3D%3E%20%7B%0A%09%09console.log(arg)%0A%09%7D)%0A%7D%29%28argument%29&arg=%5B%22A%22%2C%22B%22%2C%22C%22%5D
omnigraffle://localhost/omnijs-run?script=%28function%20logTheseItems(args)%7B%0A%09args.forEach(arg%20%3D%3E%20%7B%0A%09%09console.log(arg)%0A%09%7D)%0A%7D%29%28argument%29&arg=%5B%22A%22%2C%22B%22%2C%22C%22%5D
omnioutliner://localhost/omnijs-run?script=%28function%20logTheseItems(args)%7B%0A%09args.forEach(arg%20%3D%3E%20%7B%0A%09%09console.log(arg)%0A%09%7D)%0A%7D%29%28argument%29&arg=%5B%22A%22%2C%22B%22%2C%22C%22%5D
omniplan://localhost/omnijs-run?script=%28function%20logTheseItems(args)%7B%0A%09args.forEach(arg%20%3D%3E%20%7B%0A%09%09console.log(arg)%0A%09%7D)%0A%7D%29%28argument%29&arg=%5B%22A%22%2C%22B%22%2C%22C%22%5D
Script as Function
    

function logTheseItems(args){ args.forEach(arg => { console.log(arg) }) } functionInput = ["A","B","C"] functionString = logTheseItems.toString() encodedFunction = encodeURIComponent(functionString) inputString = JSON.stringify(functionInput) encodedInput = encodeURIComponent(inputString) targetAppName = "OmniFocus".toLowerCase() op = "%28" // open paren cp = "%29" // close paren urlString = `${targetAppName}://localhost/omnijs-run?script=${op}${encodedFunction}${cp}${op}argument${cp}&arg=${encodedInput}`

 1-5  The function to be executed by the targeted Omni application. Note that the function takes takes passed-in input in the form of a placeholder variable that represents the data string, object, or array that will be passed to the function upon script execution. This variable can be named whatever you wish, except the term “argument” which is reserved by Omni Automation for use elsewhere in the script.

 7  The data to be processed by the function as its input. This data can be in the form of a string, object, or array. In this example, it is an array of strings, each of which will be logged by the function to the target application’s automation console.

 9  The function code is converted into a string value using the toString() method.

 10  The function string is encoded using the standard JavaScript encodeURIComponent() method in preparation for its inclusion in an Omni Automation script URL.

 11  The input element is converted into a string using the stringify() function of the JavaScript JSON class.

 12  The input element string is encoded using the standard JavaScript encodeURIComponent() method in preparation for its inclusion in the Omni Automation script URL.

 14  The name of the targeted Omni application is stored in a variable in lowercase format.

 15-16  For clarity, the percent-encoding values for the opening and closing paren characters are stored in variables that will be inserted into the URL string.

IMPORTANT: the processing function needs to be passed to the targeted application as self-invoking, which means it is encased in a set of parens, directly followed by another set of parens containing the special term “argument” representing the data placed after the “&arg=” parameter in the script URL. (encoded-function-code)(argument)

When the script in the URL is executed by the target Omni application, the term “argument” will automatically be replaced with the passed decoded value of the “arg” parameter.

 17  The script URL string is created using text placeholders ${variableName} placed with the string. Note that this Omni Automation script URL takes two parameters: script= followed by the encoded script, and &arg= followed by the encoded argument data.

For this example, the resulting script URL looks similar to:

omnifocus://localhost/omnijs-run?script=%28function%20logTheseItems(args)%7B%0A%09args.forEach(arg%20%3D%3E%20%7B%0A%09%09console.log(arg)%0A%09%7D)%0A%7D%29%28argument%29&arg=%5B%22A%22%2C%22B%22%2C%22C%22%5D

Executing the Script URL

The JavaScript code for executing (opening) the script URL depends upon the host application or webpage.

For example, the Drafts application uses the openURL() function in its JavaScript implementation to open (execute) URLs.

NOTE: In Drafts, the individual automation scripts (plug-ins) for controlling Drafts are called “actions.”

The following example Drafts “action” is designed to copy the content of the current draft to the selected OmniGraffle solid graphic. It uses the openURL() function to execute the generated Omni Automation script URL:

Drafts Action: Copy Document to Selected Graphic


(() => { // wrapping the Drafts action script in a self-invoking anonymous arrow function (() => {})(); // prevents possible conflicts between Drafts script actions that may use the same variable names // OmniGraffle JavaScript Context // Omni Automation script as a function with text input function setTextForSelectedSolid(textInput){ if (document.windows[0].selection.solids.length != 1){ title = "SELECTION ERROR"; message = "Please select a single graphic."; new Alert(title, message).show(); } else { cnvs = document.windows[0].selection.canvas; graphic = document.windows[0].selection.solids[0]; currentTextSize = graphic.textSize; graphic.text = textInput; graphic.textSize = currentTextSize; } }; // Host Application JavaScript Context (1Writer, Drafts, etc.) const docContent = draft.content; const contentString = JSON.stringify(docContent); const encodedContent = encodeURIComponent(contentString); const functionString = setTextForSelectedSolid.toString(); const encodedFunction = encodeURIComponent(functionString); // construct and execute an “approvable” Omni Automation script URL app.openURL( 'omnigraffle://localhost/omnijs-run?script=' + '%28' + encodedFunction + '%29' + '%28' + 'argument' + '%29' + '&arg=' + encodedContent ); })();

For those instances where the Omni Automation script is generated by a webpage, the page’s main JavaScript script would change the value of the location property of the Window class as a way to execute the Omni Automation script URL:

Executing Script URL from Webpage


window.location = scriptURL;

The creation and execution of Omni Automation URLs by webpages is throughly detailed here.

Security

Regardless of the mechanism used to execute the Omni Automation script URL, you need to be aware of script security procedures used by all Omni applications, whether hosted on iOS, iPadOS, or macOS. These are covered in detail in the next page of this section.