Source: fvsessions.js

/// <reference path="/ss/st/fvterm/web/Scripts/FVTermParent.js" />
/// <reference path="/ss/st/fvterm/web/Scripts/FVTermMsgApi.js" />
(function ()
{
	'use strict';
	function log(text)
	{
		/* not active
		if (typeof (console) != 'undefined')
			console.log(text);
		*/
	}
/**
 * @fileOverview The FVSessions Module contains the FVSessions.Manager class function and the FVSessions.Session class
 * @author Inventu Corporation <support@inventu.com">
 * @version 1.0.20
 */
	var FVSMGR, FVSESS,
			enterKeys = 'EC123456789abcdefghijklmnoxyz';
	//trim polyfill
	if (!String.prototype.trim)
	{
		String.prototype.trim = function ()
		{
			return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
		};
	}
/**
 * @namespace FVSessions
 */
 	if (!window.FVSessions)
		window.FVSessions = {};
	FVSessions.config = {
		fvBaseUrl: '/fvterm/',
		fvTermPage: 'scterm.html',
		fvMacrosMgr: 'macros/FlowMacros.aspx',
		fvProfileMgr: 'SCProfile.aspx',
		msgAPI:true
	};

	// #region Manager class
/** The Manager Class is used to initialize the environment and host Session objects
 * In September 2023, the FVSessions.config setting msgAPI was added with a default value of true.
 * This now assumes that FVTermMsgApi.js instead of FVTermParent.js is the corresponding JS file 
 * providing the iFrame API management.  FVTermMsgApi.js utilizes inter-window messaging instead
 * of direct calls that require document.domain to be set in many environments.
 * If you are using FVSessions.js with the direct API module FVTermParent.js, you will need to set
 * FVSessions.config.msgAPI=false prior to creating a new connection.
 * @typedef { FVSessions.Manager } Manager
 */

/** @typedef {Object} Connection
 * @description Defines how a Session connects when started--matches all documented properties for the FVTermParent ConnectFVTerm function
 * @property {string} application (optional--overrides the SessionControl property of same name if defined) Name of application defined in web.config for screen recognition
 * @property {string} hostName Host name defined for the Inventu service that this session connects to
 * @property {boolean} autoStart If true the session will automatically connect to the host at start time 
 * @property {boolean} keepAliveOnClose If true, the session will not be terminated/closed by the browser logic if the user closes the browser at the desktop level
 * @property {string} userID Can be set prior to start session--this is the UserID to be associated with the session in the Inventu Service
 * @property {Session} fnSessClass Needed at constructor time--defines class for sessions, such as FVSessions.HODSession-default is FVSessions.Session
  */

/** @typedef {Object} SessionControl
 * @description Keyed by SessionID -- included in the SessionDef object passed as the control parameter to the Manager Class Constructor
 * @property {function(Session, rows, screenName)} onnewscreen Event function called any time a new screen is received from the host
 * @property {function(Session)} onclose Event function called when the session is closed
 * @property {function(Session, enterKey)} onsend Event function called whenever the user enters a screen
 * @property {string} frameId Optional frameID that this session should be opened/started in--if not set the name is 'FVSWin_' + the session id
 * @property {Connection} connection Defines how the session will connect during the Manager.StartSession function
 */

/** @typedef {Object} SessionDef
 * @property {string} application Default name of application defined in web.config for screen recognition -- can be overridden in session connection property
 * @property {function(Session, rows, screenName)} onnewscreen Default Event function called any time a new screen is received from the host--
 * can be overridden for each Session in the sessions SessionControl object
 * @property {function(Session)} onclose Default Event function called when the session is closed--
 * can be overridden for each Session in the sessions SessionControl object
 * @property {function(Session, enterKey)} onsend Default Event function called whenever the user enters a screen--
 * can be overridden for each Session in the sessions SessionControl object
 * @property {SessionControl} sessions One or more session definitions keyed by the sessionID
 */

	/** @constructor
	* @description Manager Class Constructor function--please ensure that the FVSessions.config.msgAPI is set
   * to correspond with the API JS file you are using (see FVSessions.Manager class documentation)
	* @param {Object} win The Window object for the environment this Manager will run in
   * @param {SessionDef} control Settings for the events and sessions, with objects keyed by sessionID key property for each session is connection
   * @param {function(Object)} fnReady Completion function called when ready--includes a reference to the sessions object, indexed by session ids
	*/
	FVSessions.Manager = function (win, control, fnReady)
	{
		var key, me = this, session, sessionKey, sessionChecks = [],
			sessions = (control && control.sessions) ? control.sessions : null, bindings, sessObj,
			fnSessClass = (control.fnSessClass) ? control.fnSessClass : FVSessions.Session;
		this.sessions = {};
		if (control.application)
			this.application = control.application;
		this.win = win;
		this.doc = win.document;
		if (sessions)
		{
			for (key in sessions)
			{
				if (sessions.hasOwnProperty(key))
				{
					bindings = {};
					sessObj = sessions[key];
					if (control.onalert || sessObj.onalert)
						bindings.onalert = (sessObj.onalert) ? sessObj.onalert : control.onalert;
					if (control.onsend || sessObj.onsend)
						bindings.onsend = (sessObj.onsend) ? sessObj.onsend : control.onsend;
					if (control.onclose || sessObj.onclose)
						bindings.onclose = (sessObj.onclose) ? sessObj.onclose : control.onclose;
					if (control.onnewscreen || sessObj.onnewscreen)
						bindings.onnewscreen = (sessObj.onnewscreen) ? sessObj.onnewscreen : control.onnewscreen;
					this.sessions[key] = session = new fnSessClass(key, this, sessObj, bindings);
					if (session.sessionKey)
						sessionChecks.push(session.sessionKey);
				}
			}
			if (sessionChecks.length)
			{
				this.ServerCall(FVSessions.config.fvBaseUrl + FVSessions.config.fvMacrosMgr + '?action=verifySessions', sessionChecks, { method: 'POST' }, function (statuses)
				{
					var i, id;
					for (i = 0; i < statuses.length; i++)
					{
						session = me.FindSessionByKey(sessionChecks[i]);
						session.SetStatus(statuses[i]);
					}
					if (fnReady)
						fnReady(me.sessions);
				});
			}
			else
			{
				if (fnReady)
					window.setTimeout(function () { fnReady(me.sessions) }, 1);
			}
		}
	};
	FVSMGR = FVSessions.Manager.prototype;

	/** @memberof! FVSessions.Manager.prototype
	 * @description Find a Session object based on a sessionKey
	* @param {String} sessionKey The SessionKey to use in the search
	* @returns {Session} Found session or null
	*/
	FVSMGR.FindSessionByKey = function (sessionKey)
	{
		var key;
		for (key in this.sessions)
		{
			if (this.sessions[key].sessionKey == sessionKey)
				return this.sessions[key];
		}
	}

	/** @memberof! FVSessions.Manager.prototype
	* @description Resize all Sessions
	* @param {Integer} width New Width for each Session
	* @param {Integer} height New height for each sessions
	*/
	FVSMGR.ResizeSessions = function (width, height)
	{
		var key, session;
		for (key in this.sessions)
		{
			if (!this.sessions.hasOwnProperty(key))
				continue;
			session = this.sessions[key];
			if (session.fvWin)
			{
				session.fvWin.style.width = width + 'px';
				session.fvWin.style.height = height + 'px';
			}
		}
	};

	/** @memberof! FVSessions.Manager.prototype
	 * @description Start a Session
	* @param {String} id The ID of the session - must match one of the sessions defined in the constructor
	* @param {Object} styles (Optional) - If not null, an object with property names matching styles to attach to the new session's iFrame
	* @param {Object} parentElem Parent DOM element--if not included, the document.body element will be the parent of the new iFrame
	* @param {function(Session)} fnReady Completion function called when the session has been started
	*/
	FVSMGR.StartSession = function (id, styles, parentElem, fnReady)
	{
		var key, me = this, session = this.sessions[id], parentDiv,
			fvWin = (session.fvWin) ? session.fvWin : this.doc.getElementById(session.frameId),
			url = FVSessions.config.fvBaseUrl + FVSessions.config.fvTermPage,
			connection = session.connection,
			fnConnect = (FVSessions.config.msgAPI) ? FVMsgApi.ConnectMsgFVTerm : ConnectFVTerm;

		if (!fvWin)
		{
			if (!parentElem)
				parentElem = this.doc.body;
			parentDiv = this.doc.createElement('div');
			parentDiv.style.display = 'inline';
			parentElem.appendChild(parentDiv);
			parentDiv.innerHTML = '<iframe id="' + session.frameId + '" />';
			fvWin = this.doc.getElementById(session.frameId);
			session.fvWin = fvWin;
		}
		/* If msgApi, turn fvWin into a proxy and save the real fvWin
		 */
		if (styles)
		{
			for (key in styles)
			{
				if (styles.hasOwnProperty(key))
				{
					fvWin.style[key] = styles[key];
				}
			}
		}
		if (session.status != 'offline')
		{
			connection = { "sessionKey": session.sessionKey };
			if (session.connection.keepAliveOnClose)
				connection.keepAliveOnClose = session.connection.keepAliveOnClose;
			if (this.application)
				connection.application = this.application;
		}
		//fnConnect(fvWin.id, url, connection, async function (fvApi, sessionKey) need async?
		fnConnect(fvWin.id, url, connection, async function (fvApi, sessionKey)
		{
			if (fvApi)
				await session.BindNewSession(fvApi, sessionKey);
			else
				session.connectError = sessionKey;
			if (fnReady)
				fnReady(session);
		});
	};

	/** @memberof! FVSessions.Manager.prototype
	 * @description Call a server function that supports the session manager
	* @param {String} url Action portion of the url
	* @param {Object} data (Optional) - For a post, the object that will be sent as JSON stringified null value is ignored
	* @param {Object} options (optional) properties include method (GET/POST), mimeType, credUser and credPW all optional--null is ignored
	* @param {function(Object)} fnReady Completion function called with returned object converted from JSON
	* @returns {FVSMGR.ServerCall} object
	*/
	FVSMGR.ServerCall = function (url, data, options, fnReady)
	{
		var xmlHttp = null,
			me = this, key,
			err, method = (options && options.method) ? options.method : 'GET',
			mimeType = (options && options.mimeType) ? options.mimeType : 'application/json',
			credUser = (options && options.credUser) ? options.credUser : null,
			credPW = (options && options.credPW) ? options.credPW : null,
			query = [], postData = null;
		try
		{
			if (window.XMLHttpRequest)
			{
				// If IE7, Mozilla, Safari, etc: Use native object
				xmlHttp = new XMLHttpRequest();
				if (xmlHttp.overrideMimeType)
					xmlHttp.overrideMimeType(mimeType);
			}
			else if (window.ActiveXObject)
			{
				// ...otherwise, use the ActiveX control for IE5.x and IE6
				xmlHttp = new ActiveXObject('MSXML2.XMLHTTP.3.0');
			}

			if (data)
			{
				if ((method == 'GET') ||
					(mimeType != 'application/json'))
				{
					for (key in data)
					{
						if (data.hasOwnProperty(key))
							query.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key]));
					}
				}
				if (method == 'GET')
				{
					if (url.indexOf('?') == -1)
						url += '?';
					url += query.join('&');
				}
				else
				{
					if (mimeType == 'application/json')
						postData = JSON.stringify(data);
					else
						postData = query.join('&');
				}
			}

			xmlHttp.open(method, url, true, credUser, credPW);
			xmlHttp.onreadystatechange = function ()
			{
				if (xmlHttp.readyState >= 4)
				{
					var result;
					if (xmlHttp.status == 200)
					{
						if (mimeType == 'application/json')
							result = JSON.parse(xmlHttp.responseText);
						else
							result = xmlHttp.responseText;
						me.httpError = false;
					}
					else
					{
						result = { status: 'error', code: xmlHttp.status };
						me.httpError = true;
					}
					if (fnReady)
					{
						fnReady(result);
					}
				}

			};
			if (method == 'POST')
				xmlHttp.setRequestHeader('Content-type', 'application/json; charset=utf-8');
			xmlHttp.send(postData);
		}
		catch (e)
		{
			alert(e.message);
			return null;
		}
		return xmlHttp;
	};
	// #endregion Manager Class

	// #region Session Class
/** The Session class manages an individual FVTerm session and is created using the Manager
* @typedef {FVSessions.Session} Session
*/
	/** @description Session Class Constructor function
	* @constructor
	* @param {String} id  The id for this session, used for access and naming
   * @param {Object}	mgr The owning FVSessions.Manager object
   * @param {Object} settings The source properties including connection which has connection details
   * @param {Object} bindings Any event/call forwarders
	*/
	FVSessions.Session = function (id, mgr, settings, bindings)
	{
		if (!id)
			return;
		this.id = id;
		this.mgr = mgr;
		this.settings = settings;
		this.connection = settings.connection;
		if (!this.connection.application && mgr.application)
			this.connection.application = mgr.application;
		this.status = 'offline';
		this.frameId = (settings.frameId) ? settings.frameId : 'FVSWin_' + id;
		this.storeId = 'FVSK_' + id;
		this.sessionKey = this.GetSessionKey();
		this.bindings = bindings;
		if (FVSessions.config.msgAPI)
			FVSessions.SetMsgMethods();
		else
			FVSessions.SetLocalMethods();
	};
	FVSESS = FVSessions.Session.prototype;

	/**  @memberof! FVSessions.Session.prototype
		 * @description Internal function that binds the FVTermParent events to the client's onnewscreen, 
		 * onclose, onsend and onalert (when each handler is present) in support of the FVTermMsgApi.js module
		 * implemented September of 2023, this function is flagged async as the fvApi.GetCookieEx must run
		 * with an await to retain synchronicity in the messaging environment.
		 * @param {Object} fvApi Api Control Object
		 * @param {String} sessionKey SessionKey if for active session
		 * */
	FVSESS.BindNewSession = async function (fvApi, sessionKey)
	{
		var me = this,
			realSessionKey = (sessionKey && (sessionKey.substr(0, 4) == 'FVC_')) ? await fvApi.GetCookieEx(sessionKey) : sessionKey;
		this.fvApi = fvApi;
		if (sessionKey)
			log('BindNewSession for ' + sessionKey + ' (' + realSessionKey + ')');
		else
			log('BindNewSession for failed session');
		if (sessionKey)
			this.SaveSessionKey(realSessionKey);
		this.SetStatus('started');
		fvApi.onnewscreen = function (rows, screenName)
		{
			me.screenName = screenName;
			if (me.bindings.onnewscreen)
				me.bindings.onnewscreen(me, rows, screenName);
		};
		fvApi.onclose = function (closeType)
		{
			me.SetStatus('offline'); // clears sessionkey
			if (me.bindings.onclose)
				me.bindings.onclose(me, closeType);
		};
		fvApi.onsend = function (enterKey)
		{
			if (me.bindings.onsend)
				return me.bindings.onsend(me, enterKey);
			else
				return true;
		};
		fvApi.onalert = function (alertObj, msg, bgColor, fnOnClick, buttons)
		{
			if (me.bindings.onalert)
				return me.bindings.onalert(me, alertObj, msg, bgColor, fnOnClick, buttons);
			else
				return null; // not handle
		};
	};

	/**  @memberof! FVSessions.Session.prototype
	* @description Internal use only function used to set functions when FVSessions.config.msgAPI is true
	*/
	FVSessions.SetMsgMethods = function ()
	{
		/**  @memberof! FVSessions.Session.prototype
		* @description Close the Session if active
		*/
		FVSESS.Close = function ()
		{
			if (this.status != 'offline')
			{
				try
				{
					this.fvApi.CloseTerm();
				}
				catch (e) { }
			}
		};

		/**  @memberof! FVSessions.Session.prototype
		* @description Control if the session has focus - Note for MsgAPI need to await this if you want it
		* to be synchronous
		* @param {boolean} focusOn If true the session will have focus set, if false, focus will be off and locked off until 
		* @param {boolean} activate If true, when focusOn is true, will also set browser focus into the FVTerm body
		* set back on with this call
	   * @returns {Promise} For MsgAPI returns a Promise
		*/
		FVSESS.SetFocus = async function (focusOn, activate)
		{
			var me = this;
			return new Promise(async (resolve) =>
			{
				if (this.status != 'offline')
				{
					try
					{
						if (focusOn)
						{
							await me.fvApi.EmActive(2, 'FVSession');
							if (activate)
								await me.fvApi.SCSetTermFocus();
						}
						else
						{
							await me.fvApi.EmActive(-1, 'FVSession');
						}
						resolve(true);
					}
					catch (e)
					{
						log('Exception during SetFocus=' + e);
						resolve(false);
					}
				}
			});
		};

		/** @memberof! FVSessions.Session.prototype
		* @description Read from the active screen--will trim blanks from the text that is read--
	   * Note that for MsgAPI you need to await this call to get the right value
		* @param {Integer} row Row to read from where 1 is the first row
		* @param {Integer} column Column to read from where 1 is the first column
		* @param {Integer} length Length to read--if row/column is entry field this is ignored
		* @returns {String} Text from the screen at row, column for length
		*/
		FVSESS.GetScreenText = async function (row, column, length)
		{
			return this.fvApi.GetScnTextEx(row, column, length); // returns a promise
		};
		/**  @memberof! FVSessions.Session.prototype
		 * @description Send a set of keys to the session, should end with Enter or Function key
		* @param {String} transactions  Transactions to run separated by the vertical bar (|) -- grammar for each transaction: 
		* <ul style=list-style:none;">
		* <li>F^row^column=move cursor to location;</li>
		* <li>H^enterKeys^waitForScreen= enterkeys that end with enter or function key and waitForScreen is a screen in the active application's definition file</li>
		* <li>I^keystrokes= keystrokes are a mix of text and @T for tab and so on (default action if no I^ is present at start of the transaction)</li>
		* <li>T^text^row^column=Enter text into an unprotected field at row and column where each starts with 1</li>
		* <li>S^milliseconds=Sleep for the milliseconds like S^2000 for 2 second sleep</li>
		* <li>W^milliseconds^waitForScreen=Sleep for the milliseconds like S^2000 for 2 second sleep but if screen comes in move on</li>
		* </ul>
		* Example: H^RMIS@E^RMIS|T^123456789^3^23|T^10081958^5^23|H^@E^RMIM will enter the transaction "RMIS" then wait for a screen identified as RMIS;
		* then it will key "123456789" at a field at row 3 column 23; then a field "10081958" at row 5, column 23; then will send an ENTER and wait again for RMIS to appear
		* @param {function(screenName)} onComplete  Optional completion function for when the transactions are completed
		* @param {boolean} bubble Set true for screen updates to be bubbled to the onload
		*/
		FVSESS.Transactions = function (transactions, onComplete, bubble)
		{
			this.fvApi.Transactions(transactions, onComplete, bubble);
		};

		/**  @memberof! FVSessions.Session.prototype
		* @description Set a field's value for MsgAPI use await to ensure synchronous operation
		* @param {String} text  Text to set into an unprotected entry field on the screen
		* @param {integer} row  Row to set the text on starting with 1
		* @param {integer} column Column to set thet ext on starting with 1
	   * @returns {Boolean} Function succeeded (MsgAPI only)
		*/
		FVSESS.SetField = function (text, row, column)
		{
			var me = this;
			return new Promise(async (resolve) =>
			{
				var ok=await me.fvApi.SetField(row, column, text);
				resolve(ok);
			});
		};
	};

	/**  @memberof! FVSessions.Session.prototype
	* @description Internal use only function used to set functions when FVSessions.config.msgAPI is false
	*/
	FVSessions.SetLocalMethods = function ()
	{
		/** @memberof! FVSessions.Session.prototype
		 * @description Split keys so that hllapi enters and function keys break into individual objects -- 
		 * NOT SUPPORTED for MsgAPI=true
		* @param {String} text Hllapi string with @ mnemonics (not [enter] style for now!)
		* @returns {Object}	Array of strings where each has keys ending with an AID (enter/function)
		*/
		FVSESS.SplitKeys = function (text)
		{
			try
			{
				var i, ch, parts = [], keys, start = 0, state = 0;
				for (i = 0; i < text.length; i++)
				{
					ch = text.substr(i, 1);
					switch (state)
					{
						case 0:
							start = i;
							state = 1;
						// fall through
						case 1:
							if (ch == '@')
								state = 2;
							break;
						case 2:
							if (enterKeys.indexOf(ch) != -1)
							{
								parts.push(text.substr(start, (i - start) + 1));
								state = 0;
							}
							else
								state = 1;
							break;
					}
				}
				return parts;
			}
			catch (e)
			{
				log('SplitKeys exception=' + e);
				return [text];
			}
		};

		FVSESS.Close = function ()
		{
			if (this.status != 'offline')
			{
				try
				{
					this.fvApi.fvWin.CloseTerm();
				}
				catch (e) { }
			}
		};

		FVSESS.SetFocus = function (focusOn, activate)
		{
			if (this.status != 'offline')
			{
				try
				{
					if (focusOn)
					{
						this.fvApi.fvWin.EmActive(2, 'FVSession');
						if (activate)
							this.fvApi.fvWin.SCSetTermFocus();
					}
					else
					{
						this.fvApi.fvWin.EmActive(-1, 'FVSession');
					}
				}
				catch (e) { }
			}
		};

		FVSESS.GetScreenText = async function (row, column, length)
		{
			return new Promise((resolve) =>
			{
				resolve(this.fvApi.GetScnTextEx(row, column, length));
			});
		};

		FVSESS.Transactions = function (transactions, onComplete, bubble)
		{
			var me = this, lastScreen = this.screenName, timeoutHandle = null, atTran = 0, parts, tran, wait = false, waitingFor = '*', trans = transactions.split('|'),
				topHasFocus = this.fvApi.fvWin.topHasFocus,
				nextTran = function ()
				{
					atTran++;
					if (timeoutHandle)
					{
						clearTimeout(timeoutHandle);
						timeoutHandle = null;
					}
					if (atTran < trans.length)
					{
						if (trans[atTran] == '')
							nextTran();
						else
							window.setTimeout(function () { innerExec() }, 1); // let current screen event complete!
					}
					else
					{
						fixFocus();
						if (onComplete)
							onComplete(lastScreen);
					}
				},
				fixFocus = function ()
				{
					if (me.fvApi.fvWin.topHasFocus != topHasFocus)
						me.fvApi.fvWin.EmActive((topHasFocus) ? 1 : 0, 'FVSess.Transactions');
					//log('Transactions end topHasfocus=' + me.fvApi.fvWin.topHasFocus);
				},
				screenWaiter = function (rows, screenName)
				{
					//log(me.id + ' Transactions screenWaiter waitingFor=' + waitingFor + ' result screenName=' + screenName);
					lastScreen = screenName;
					if (waitingFor == '*') // any screen
						nextTran();
					else
					{
						if (screenName != waitingFor)
							me.fvApi.fnNextScreen = screenWaiter;
						else
							nextTran();
					}
					return bubble; // determines if next screen is bubbled up
				},
				innerExec = function ()
				{
					tran = trans[atTran];
					parts = (tran.indexOf('^') == -1) ? ['I', tran] : tran.split('^');
					//log(me.id + ' ('+me.sessionKey+') Transactions innerExec atTran=' + atTran + ' trans.length=' + trans.length+' transaction=' + tran);
					try
					{
						switch (parts[0])
						{
							case 'F': // move cursor to location
								me.fvApi.SetCursor(parseInt(parts[1]), parseInt(parts[2]));
								nextTran();
								break;
							case 'H': // send hllapi and wait for screen
								waitingFor = parts[2];
								//log(me.id + ' Transactions send and wait=' + parts[1] + ', ' + waitingFor);
								me.fvApi.SendKeys(parts[1], screenWaiter);
								timeoutHandle = window.setTimeout(function ()
								{
									// If we hit this a wait function went longer than 30 seconds
									atTran = trans.length;
									fixFocus();
									if (onComplete)
										onComplete(lastScreen, 'TIMED-OUT waiting for ' + waitingFor);
								}, 30000);
								break;
							case 'W': // wait for screen or timeout
								waitingFor = parts[2];
								me.fvApi.fnNextScreen = screenWaiter;
								timeoutHandle = window.setTimeout(function ()
								{
									// If we hit this a wait function went longer than wait point
									nextTran();
								}, parseInt(parts[1]));
								break;
							case 'I': // just perform keystrokes
								waitingFor = '*';
								//me.fvApi.SendKeys(parts[1], screenWaiter);
								if (me.TypeKeys(parts[1], screenWaiter))
									nextTran();
								break;
							case 'T': // Type text into row column
								me.TypeAtRowCol(parts[1], parseInt(parts[2]), parseInt(parts[3]));
								nextTran();
								break;
							case 'S':
								window.setTimeout(function () { nextTran(); }, parseInt(parts[1]));
								break;
							default: // extra | maybe?
								nextTran();
								break;
						}
					}
					catch (e)
					{
						log(me.id + ' Transactions innerExec exception=' + e);
					}
				}
			bubble = (typeof (bubble) == 'undefined') ? false : bubble;
			//log('Transactions start topHasfocus=' + this.fvApi.fvWin.topHasFocus);		
			innerExec();
		};
		/** @memberof! FVSessions.Session.prototype
		* @description Type HLLAPI string starting at current row and column
		* NOT SUPPORTED for MsgAPI=true
		* @param {String} text  Text to type into unprotected entry field(s) on the screen, can include @T for tab and end
		* with @E or other AID string, which will be sent to the host for processing
		* @param {function(screenRows,screenName)} screenWaiter Provides callback instead of onLoad
		* @returns {boolean} Keys were typed OK
		*/
		FVSESS.TypeKeys = function (text, screenWaiter)
		{
			var me = this, i, ch, keys, state = 0,
				ev, kev, em = me.fvApi.emSess,
				fvWin = me.fvApi.fvWin,
				SetEV = function (chr)
				{
					ev = {};
					if (chr)
						ev.keyCode = chr.charCodeAt(0);
					kev = fvWin.SCKEV(em, -1, ev);
					kev.apiCall = true;
				};
			try
			{
				for (i = 0; i < text.length; i++)
				{
					ch = text.substr(i, 1);
					switch (state)
					{
						case 0:
							if (ch == '@')
								state = 1;
							else
							{
								// type character
								SetEV(ch);
								fvWin.SCKeyPress(em, ev, kev);
							}
							break;
						case 1:
							if (enterKeys.indexOf(ch) != -1) // AID, end of this!
							{
								me.fvApi.SendKeys(text.substr(i - 1), screenWaiter);
								return false;
							}
							else
							{
								state = 0; // assume single character
								SetEV();
								switch (ch)
								{
									case 'D':
										fvWin.SCDelete(em, kev);
										break;
									case 'q':
										fvWin.SCCursorEnd(em, kev);
										break;
									case '0':
										fvWin.CursorHome(em, kev, true);
										break;
									case 'A':
										state = 2;
										break;
									case 'C':
										state = 3;
										break;
									case 'F': // FieldExit
										fvWin.FieldExit(em, kev, 'NT');
										break;
									case 'T': // tab
										fvWin.SCTab(em, ev, kev);
										break;
									case 'B':
										fvWin.SCBackTab(em, ev, kev);
										break;
									case 'L':
										fvWin.CursorLeft(em, kev);
										break;
									case 'U':
										fvWin.CursorUp(em, kev);
										break;
									case 'Z':
										fvWin.CursorRight(em, kev);
										break;
									case 'V':
										fvWin.CursorDown(em, kev);
										break;
									default:
										log('TypeKeys unknown HLLAPI Code-@' + ch);
										break;
								}
							}
							break;
						case 2: // first char was 'A'
							if (ch == 'E')
								fvWin.FieldExit(em, kev, '+');
							else
								log('TypeKeys unknown HLLAPI Code-@A@' + ch);
							state = 0;
							break;
						case 3: // first char was 'C'
							if (ch == 'H')
								fvWin.SCBackSp(em, kev);
							else
								log('TypeKeys unknown HLLAPI Code-@C@' + ch);
							state = 0;
							break;
					}
				}
			}
			catch (e)
			{
				log('TypeKeys exception=' + e);
			}
			return true;
		};

		/**  @memberof! FVSessions.Session.prototype
		* @description Type text starting at a row and column - if length of text exceeds first field will autoskip to next
	   * NOT SUPPORTED FOR MsgAPI=true
		* @param {String} text  Text to type into unprotected entry field(s) on the screen
		* @param {integer} row  Row to type the text on starting with 1
		* @param {integer} column Column to type the text on starting with 1
		*/
		FVSESS.TypeAtRowCol = function (text, row, column)
		{
			var ev = {}, kev;
			ev.keyCode = 600;
			//log('TypeAtRowCol: "' + text + '" @' + row + ',' + column);
			this.fvApi.SetCursor(row, column);
			kev = this.fvApi.fvWin.SCKEV(this.fvApi.emSess, -1, ev);
			kev.apiCall = true;
			this.fvApi.fvWin.SCPaste(this.fvApi.emSess, kev, text);
		};

		FVSESS.SetField = function (text, row, column)
		{
			this.fvApi.SetField(row, column, text);
		};
	};

	/**  @memberof! FVSessions.Session.prototype
   * @description Enter the active screen using a hllapi string
	* @param {String} enterKeys  Should be one or more keys with an enter/function/action key at the end like @E or @C ([enter] or [clear] or @T for Tabbing with ASCII host)
	* @param {function(screenName)} onComplete  Optional completion function for when the screen has been entered
	*/
	FVSESS.EnterScreen = function (enterKeys, onComplete)
	{
		this.fvApi.SendKeys(enterKeys, function (rows, screenName)
		{
			if (onComplete)
				onComplete(screenName);
		});
	};

	/**  @memberof! FVSessions.Session.prototype
	 * @description Set the status of the Session
	* @param {String} newStatus  Status which includes offline, started (needs logon) and ready (not on any logon screens)
	*/
	FVSESS.SetStatus = function (newStatus)
	{
		this.status = newStatus;
		switch (newStatus)
		{
			case 'offline':
				this.SaveSessionKey();
				break;
		}
	};

	/**  @memberof! FVSessions.Session.prototype
	 * @description Get the current session key from local storage
	* @returns {String} Sessionkey or null if not found
	*/
	FVSESS.GetSessionKey = function ()
	{
		var sessionKey = localStorage.getItem(this.storeId);
		log('GetSessionKey for ' + this.storeId + '=' + sessionKey);
		return sessionKey;
	};

	/**   @memberof! FVSessions.Session.prototype
	 * @description Save a sessionKey for this session to local storage
	* @param {String} sessionKey SessionKey to save -- if null, the localstorage will be cleared
	*/
	FVSESS.SaveSessionKey = function (sessionKey)
	{
		if (!this.connection.keepAliveOnClose)
			return; // only save sessionKey when keepAliveOnClose is true...
		if (!sessionKey)
		{
			this.sessionKey = null;
			log('SaveSessionKey deleted key for ' + this.storeId);
			localStorage.removeItem(this.storeId);
		}
		else
		{
			log('SaveSessionKey saved ' + sessionKey + ' for ' + this.storeId);
			this.sessionKey = sessionKey;
			localStorage.setItem(this.storeId, sessionKey);
		}
	};
	// #endregion Session Class
}());