Simphony JavaScript Extensibility

JavaScript Extensibility For Transaction Services

Introduction

JavaScript Extensibility was made available in Simphony 19.6 and has been further enhanced in Simphony 19.7. JavaScript Extensibility improved on SIM/C# with the following characteristics:

  • available in Windows/Linux/Android
  • available in OPS/Transaction Services/Extensibility Services
  • the same base API for all host type/OS combinations

Details

C# and SIM Extensibility is only available for OPS. There is a C# plugin support for Transaction Services but it is limited.

JavaScript Extensibility applications can be written for Transaction Services as well as OPS. The goals of Transaction Services JavaScript Extensibility are:

  • same base API (SimphonyExtensibilityAPI)
  • same JavaScript engine environment
  • same DataStore access
  • ability to use same code for Transaction Services and OPS (within reason)

Unfortunately, the operating environment dictates a different JavaScript extensibility model. This table attempts to summarize these differences

OPS Transaction Services
Root engine Performs all transaction business logic Minimal, used to configure service script and possibly read common configuration. In general, we say that the Transaction Services root script is “administrative”, it is not intended to contain any transaction business logic.
Service Engine Not applicable. Performs all transaction business logic.
Lifetime The root script lifetime exists for the lifetime of servicehost.exe. Once it is created it is never destroyed until service host is shut down OR the extension application is updated The root script lifetime exists for the lifetime of servicehost.exe.
The service engine exists for the lifetime of the Transaction Services request. That is, a new service engine is created when the Transaction Services request begins and is destroyed when the Transaction Services request ends. A single service script can subscribe to multiple events and keep persistent data, but only for the lifetime of the Transaction Services request. There can be multiple service engines active at a single time, each with its own address space.
Datastore Extensibility Datastore Extensibility Datastore
Context standard OpsContext A limited “BaseContext”. The TS context contains simple configuration information such as WorkstationID. Please see Context (any Host-Type) in the SimphonyExtensibilityAPI section.
Events standard OPS extensibility events available to SIM and C# A limited set of events. Please see Events available for Transaction Services in the POS Events section
Transaction SIM/C#/JavaScript retain the classic interactive event model for transactions: BeginCheck, MenuItem, MenuItem, FinalTender, … Transaction Services requests are “batch” oriented. There is no concept of BeginCheck, MenuItem confirm, etc. The entire check is presented to the Transaction Services service engine and is processed in one event.

API Design Considerations

OPS ordering is interactive while Transaction Services ordering is batch oriented.

To support this a new Transaction Services event API was created (SubmitCheck). This SubmitCheck API was then made available to OPS. Thus, the same code could be reused in both host types.

Lifetime Model

OPS is inherently single threaded. Only one check can be active a time. The OPS root script is always present. It is only destroyed when the ext app has changed or service host shuts down.

Transaction Services fields multiple simultaneous requests. Therefore, multiple active Transaction Services Service JavaScript engines need to be created, one for each Transaction Services request. Each Transaction Services Service JavaScript engine has its own address space; the Transaction Services engines can only share data through the API ApplicationObjects map.

The Transaction Services root script defines the Transaction Services engine scripts, but Transaction Services will use this configuration to instantiate a Transaction Services engine for each request.

TS Event Model

Event Model

A new set of events was created for Transaction Services service engines. They are:

  • SubmitCheck: raised when a check is submitted
  • SubmitCheckRejected: raised if an extensibility application aborts the SubmitCheck event
  • SubmitCheckNotification: raised after all processing, successful or aborted

In addition, the following events exist, similar to the OPS events:

  • Init: raised when Transaction Services engine is created
  • Exit: raised when all transaction processing is done
  • OpsCustomReceiptEvent: raised for custom receipt processing, similar to OPS

The following diagram attempts to show how events are processed for each Transaction Services request.

TS Event Model

Contrast this with how OPS processing multiple checks in sequence.

OPS Event Model

Context

All JavaScript extensibility applications are provided a “Context” object. It is accessed with SimphonyExtensibilityAPI.Environment.Context. However, the underlying Context object is not the same, based on the host type.

  • OPS: OpsContext, equivalent to the SIM/C# OpsContext.
  • Transaction Services: BaseHostContext, see definition below.

BaseHostContext contains a minimal set of configuration parameters. It is notably less featured than OpsContext, as much of what OpsContext provides would not be valid in Transaction Services. (dialog requests, current check, etc).

Context (BaseHostContext)

Method / Property Description
string MapRootPath( string fileName ) Get application root path based on file-name provided
string PropertyNumber Property number as configured in EMC
long PropHierStrucID internal Hierarchy Structure ID for this Property
int PropertyID internal Property ID
string PropertyName Property name as configured in EMC
string WorkstationName Workstation name as configured in EMC
int WorkstationNumber Workstation number as configured in EMC
int WorkstationID internal Workstation ID
string HostName Host-name of the device
int OrganizationId internal Organization ID
int LocationId internal Location ID
int CurrentLangId internal, current Language ID
int CurrentLangObjNum current Language object number
Networking Networking Networking object
int ServiceHostID internal ServiceHost ID
string EGatewayURL URL of the EGateway resource
string Version Version string of Simphony
string IPaddress IP address of the device
ApiOperatingSystem OperatingSystem see ApiOperatingSystem members below

ApiOperatingSystem

Method / Property Description
bool IsWin32 True when running under Windows
bool IsNotWin32 True for both Android and Linux
bool IsAndroid True when running under Android
bool IsLinux True when running under Linux

Multiple Extensibility Applications

Multiple Extensibility applications can be configured and executed in a single ServiceHost OPS/Transaction Services workstation.

Just as with OPS, multiple Extensibility applications can subscribe to the SubmitCheck event, each which can abort the event. The SubmitCheck event model was designed to handle multiple Extensibility applications, and extensibility writers should take care when writing code.

Consider 3 extension applications configured: JS-X, JS-Y, JS-Z. Each subscribes to the SubmitCheck events, each can abort the SubmitCheck event.

OPS Event Model

Notes:

  • The SubmitCheckRejected event will be run for ext apps that processed the SubmitCheck event. In the drawing above, if JS-Y aborted the SubmitCheck, only JS-X will get the rejected event. JS-Z will not as it never ran the SubmitCheck event.
  • The SubmitCheckNotification event will be run for all ext apps, whether the check was accepted or rejected.

Events

SubmitCheckEventArgs

Property Description
CheckBase Check Check header and detail.
Check.Header: same as OPS OpsContext.Check
Check.Detail: same as OPS OpsContext.CheckDetail
string RejectedMessage Ext. Apps can set this if they abort a SubmitCheck event.

SubmitCheckRejectedEventArgs

Property Description
string RejectedMessage Message optionally set by extension application that aborted the event
string ExtAppThatAborted The name of the extension application that aborted the event
IList<string> ExtAppsThatExecuted List of application that processed the SubmitCheck event
bool AbortedByExtApp true if an ext app aborted the event, false if an error occurred within transaction services core code

SubmitCheckNotificationEventArgs

Property Description
bool Success true if SubmitCheck completed successfully
string RejectedMessage see SubmitCheckRejectedEventArgs
string ExtAppThatAborted see SubmitCheckRejectedEventArgs
IList<string> ExtAppsThatExecuted see SubmitCheckRejectedEventArgs
bool AbortedByExtApp see SubmitCheckRejectedEventArgs

Sample Code

The sample code:

  • subscribes to the SubmitCheck*, Receipt, and Init/Exit events
  • adds extensibility detail to the check header
  • adds extensibility detail to each menu item detail
  • optionally aborts the SubmitCheck event

Notes on code:

  • the sample code uses a library that works in OPS and TS
  • the OPS and TS root scripts do very little, all logic is handled in lib.js
  • there are a few places in lib.js that behave differently in OPS vs. TS
  • most of the events are empty. They only log their execution. The SubmitCheck event handles most of the logic.

OPS root script

import * as _lib from "lib";
_lib.configureSubmitCheck();

TS root script

import * as _lib from "lib";
var _api = SimphonyExtensibilityAPI;

_api.Eventing.SubscribeToEvent('InitEvent', OnInitEvent);

//--------------------------------------------------------------------------
function OnInitEvent(sender, args)
{
 // configure the TS service script
 let parms = _api.Runtime.AllocateServiceParameters();
 parms.ContentName = 'ts-service';
 _api.Runtime.SetService(parms);
 return _api.Eventing.Continue;
}

TS service script

import * as _lib from "lib";
_lib.configureSubmitCheck();

lib.js script

var _api = SimphonyExtensibilityAPI;
var _isOPS = _api.Runtime.HostType == _api.Common.HostType.OPS;

//--------------------------------------------------------------------------
export function configureSubmitCheck()
{
 log('configureSubmitCheck');

 // subscribe to transaction events
 _api.Eventing.SubscribeToEvent('SubmitCheckEvent', OnSubmitCheckEvent);
 _api.Eventing.SubscribeToEvent('SubmitCheckRejectedEvent', OnSubmitCheckRejectedEvent);
 _api.Eventing.SubscribeToEvent('SubmitCheckNotificationEvent', OnSubmitCheckNotificationEvent);
 _api.Eventing.SubscribeToEvent('OpsCustomReceiptEvent', OnOpsCustomReceiptEvent);

 // lifetime events.
 // these are not as useful in OPS. These would most likely be subscribed to by
 // TS service scripts and handled differently in OPS.
 var prefix = _isOPS ? "Ops" : "";
 _api.Eventing.SubscribeToEvent(prefix + 'InitEvent', OnInitEvent);
 _api.Eventing.SubscribeToEvent(prefix + 'ExitEvent', OnExitEvent);
}

//--------------------------------------------------------------------------
function OnInitEvent(sender, args)
{
 log('OnInitEvent');
}

function OnExitEvent(sender, args)
{
 log('OnExitEvent');
}

//--------------------------------------------------------------------------
function OnSubmitCheckEvent(sender, args)
{
 log('OnSubmitCheckEvent');

 // allocate text extensibility info
 let textExtInfo = _api.Common.AllocateExtensibilityDataInfo(`${_api.Runtime.ApplicationName} check text ext dtl`, "ExtDataName-Header", "some data for header");
 textExtInfo.SetPrintOnDisplayOnly();
 textExtInfo.VisibleToTransactionServices = true;

 // add to check header
 let header = args.Check.Header;
 header.AddExtensibilityData(textExtInfo);

 // iterate through check detail, add ext data to all menu items
 let detail = args.Check.Detail.Scriptable;
 for (let i = 0; i < detail.Count; i++)
 {
     let dtlItem = detail[i];
     if (dtlItem.DetailType == _api.Common.CheckDetailType.DtlTypeMi)
     {
         let dtlTextExtInfo = _api.Common.AllocateExtensibilityDataInfo(`${_api.Runtime.ApplicationName} mi dtl ext dtl`, "ExtDataName-MiDtl", "some data for mi dtl [" + (i+1) + "]");
         dtlTextExtInfo.VisibleToTransactionServices = true;
         dtlItem.AddExtensibilityData(dtlTextExtInfo);
     }
 }

 // now we need to determine whether to allow/cancel the check.
 // ops will ask the user whether to cancel

 // if ops ask a question, otherwise reject if cover count > 1
 let acceptCheck = false;
 if (_isOPS)
     acceptCheck = _api.Environment.Context.AskQuestion(`${_api.Runtime.ApplicationName}: SubmitCheck: continue?`);
 else
     acceptCheck = (header.Covers <= 1);

 if (acceptCheck)
 {
     return _api.Eventing.Continue;
 }
 else
 {
     args.RejectedMessage = `${_api.Runtime.ApplicationName}: rejected check`;
     return _api.Eventing.AbortEvent;
 }
}

//--------------------------------------------------------------------------
function OnSubmitCheckRejectedEvent(sender, args)
{
 log('OnSubmitCheckRejectedEvent');

 return _api.Eventing.Continue;
}

//--------------------------------------------------------------------------
function OnSubmitCheckNotificationEvent(sender, args)
{
 log('OnSubmitCheckNotificationEvent');

 // format for dialog
 if (_isOPS)
 {
     let msg = formatRejectedData(args, '\n');
     _api.Environment.Context.ShowMessage(`${_api.Runtime.ApplicationName}: ${msg}`);
 }

 // format for log file
 log(formatRejectedData(args, ''));

 return _api.Eventing.Continue;
}

//--------------------------------------------------------------------------
function formatRejectedData(args, separator)
{
 let line = `Event: [${args.EventID}] ${separator}`;
 line += `Success [${args.Success}] ${separator}`;
 line += `RejectedMessage [${args.RejectedMessage}] ${separator}`;
 line += `ExtAppThatAborted [${args.ExtAppThatAborted}] ${separator}`;
 line += `AbortedByExtApp [${args.AbortedByExtApp}] ${separator}`;
 line += `ExtAppsThatExecuted:${separator} `;
 for (let i = 0; i < args.ExtAppsThatExecuted.Count; i++)
     line += `[${args.ExtAppsThatExecuted[i]}] ${separator}`;
 return line;
}

//------------------------------------------------------------------
function OnOpsCustomReceiptEvent(sender, args)
{
 inform(args.EventID);
}

//------------------------------------------------------------------
export function inform(msg)
{
 if (_isOPS)
     _api.Environment.Context.ShowMessage(`${_api.Runtime.ApplicationName}: ${msg}`);
 log(msg);
}

//------------------------------------------------------------------
function log(msg)
{
 console.log(`${_api.Logger.Prefix}; ${msg}`);
 _api.Logger.LogAlways(msg);
}