Name

sn_appclient.UpdateChecker

Description

App Client class responsible for checking the repository for app updates.

Script

var UpdateChecker = Class.create();
UpdateChecker.prototype = {
  initialize: function(scheduledInstallerPayload) {
  	this._quiet = gs.getProperty("sn_appclient.http_check_updates_quietly", "true") == "true";
  	this.json = new global.JSON();
  	this.versionComparator = new VersionComparator();
  	this.glideAppLoader = new GlideAppLoader();
  	this.appNotificationConfig = this._getAppNotificationConfig();
  	this.totalAppNotificationCount = 0;
  	this.ENTITLEMENT_REVOKED = "entitlement_revoked";
  	this.TRIAL_DEACTIVATION_REQUESTED = "trial_deactivation_requested";
  	this.NOTIFICATION_GROUP_NAME = "StoreAppNotifications";
  	this.VERSION_TYPE = {
  		INSTALLED: "installed",
  		MOST_RECENT: "most_recent"
  	};
  	this.downloadLogoList = [];
  	this.constants = {
  		"NO_LOGO_EXIST": "no_logo_exist"
  	};
  	this.canSendNotification = gs.getProperty("com.snc.unified_plugin.connect.notification.enabled", "false") == "true";
  	this.processDeltaAppsOnly = gs.getProperty("sn_appclient.process.delta.apps.only", "true") == "true";
  	this.isInstanceOffline = gs.getProperty("sn_appclient.app.install.offline", "false") == "true";
  	this.appManagerCacheEnabled = gs.getProperty("sn_appclient.enable_app_manager_cache", "false") == "true";
  	this.spaceRegex = /\s/g;
  	this.buildName = new GlideUpgradeUtil().getCurrentBuildName().replace(this.spaceRegex, "").toLowerCase();
  	this.notCertifiedAppBlockMessage = gs.getMessage('App not compatible with your instance.');
  	this.notCertifiedAppIndicator =  [{"id":"incompatible", "message": gs.getMessage("Not certified"), "order": 1, "show_as_filter": true, "tooltip": gs.getMessage("App is not yet certified for this platform release."), "type": "error"}];
  	this.checkAppEntitlements = scheduledInstallerPayload;
  	this.specificAppsToUpdate = "[]";
  	this.appsUpdatedInDB = {};
  },

  _getAppNotificationConfig: function() {
  	return {
  		"appAvailableForInstall": {
  			"count": 0,
  			"filter":[
  			{
  				"category": gs.getMessage("License Status"),
  				"id": "sub"
  			},
  			{
  				"category": gs.getMessage("Obtained"),
  				"id": "not_installed"
  			},
  			{
  				"category": gs.getMessage("Listing type"),
  				"id": "show_apps_only"
  			}],
  			"linkText": gs.getMessage("Apps available for install")
  		},
  		"appAvailableForUpdate": {
  			"count": 0,
  			"filter":[
  			{
  				"category": gs.getMessage("Obtained"),
  				"id": "updates"
  			},
  			{
  				"category": gs.getMessage("Listing type"),
  				"id": "show_apps_only"
  			}],
  			"linkText": gs.getMessage("App Updates")
  		},
  		"appDeactivationRequests": {
  			"count": 0,
  			"filter":[
  			{
  				"category": gs.getMessage("Indicators"),
  				"id": "trial_deactivation_requested"
  			},
  			{
  				"category": gs.getMessage("Listing type"),
  				"id": "show_apps_only"
  			}],
  			"linkText": gs.getMessage("App deactivation requests")
  		},
  		"appEntitlementRevoked": {
  			"count": 0,
  			"filter":[
  			{
  				"category": gs.getMessage("Indicators"),
  				"id": "entitlement_revoked"
  			},
  			{
  				"category": gs.getMessage("Listing type"),
  				"id": "show_apps_only"
  			}],
  			"linkText": gs.getMessage("Apps with entitlement revoked")
  		},
  	};
  },

  updateClientCallsPropertyFromAppRepo: function() {
  	try {
  		var gu = new GlideUpgradeUtil();
  		var parms = {
  			glide_build: gu.getCurrentBuild(),
  			glide_build_name: gu.getCurrentBuildName(),
  			applications: this._getInstalledApplicationsJSON()
  		};
  		var checkClientCallsResponse = new ScopedAppRepoRequest("client_calls_allowed")
  			.setParameter("glide_build", parms.glide_build)
  			.setParameter("glide_build_name", parms.glide_build_name)
  			.setParameter("applications", parms.applications)
  			.setQuiet(this._quiet)
  			.post();
  		var checkClientCalls = this.json.decode(checkClientCallsResponse);
  		if (gs.nil(checkClientCalls) || gs.nil(checkClientCalls.client_calls_allowed))
  			return;

  		gs.debug("Got ({0}) client calls allowed: {1}", checkClientCalls.client_calls_allowed, this.json.encode(checkClientCalls));

  		var currentPropertyValue = gs.getProperty("sn_appclient.client_calls_allowed", "true");
  		var newPropertyValue = checkClientCalls.client_calls_allowed == "true";
  		if (currentPropertyValue != newPropertyValue) {
  			gs.debug("changing property value sn_appclient.client_calls_allowed from ({0}) to ({1})", currentPropertyValue, newPropertyValue);
  			var props = {
  				"sn_appclient.client_calls_allowed": newPropertyValue + ''
  			};
  			new GlideAppLoader().setAuthorProperties(props);
  		}
  	} catch (e) {
  		gs.error("Unable to parse response from App Repo. The server may not be active >> {0}", checkClientCallsResponse);
  	}
  },

  /**
   * Check central app repo for available updates
   * Refreshes the local data model and UI to reflect any changes
   */
  checkAvailableUpdates:/*void*/ function(isInteractive, honourChecksum, args, skipUpdateMutexCheck) {
  	gs.info("appNotificationConfig: " + JSON.stringify(this.appNotificationConfig));
  	gs.info("checkAvailableUpdates({0}, {1}, {2})", [isInteractive, honourChecksum, skipUpdateMutexCheck]);

  	if(!gs.nil(args) && !gs.nil(args.specificAppsToUpdate))
  		this.specificAppsToUpdate = JSON.stringify(args.specificAppsToUpdate);

  	if(gs.nil(args))
  		args = {};

  	if(args.hasOwnProperty('appManagerCacheEnabled')) {
  		this.appManagerCacheEnabled = args.appManagerCacheEnabled;
  	}

  	this.processDeltaAppsOnly = this.processDeltaAppsOnly && isInteractive;
  	var storePayload = this._fetchAppUpdates(isInteractive, honourChecksum, args, !skipUpdateMutexCheck);
  	if (!storePayload || storePayload.length <= 0) {
  		this.refreshStoreResponseCache(args);
  		return;
  	}

  	if (!this.isInstanceOffline && this.processDeltaAppsOnly) {
  		this._processDeltaPayload(storePayload, args);
  		return;
  	}

  	var updates = this._getUpdates(storePayload);
  	if (!updates) return;
  	
  	//remove apps where a local version exists
  	this._filterLocalApps(updates.appMap);

      //delete existing sys_remote_apps if they aren't in the updates
      this._removeInaccessibleRemoteApps(updates.appMap);

      // Delete existing sys_app_customizations if they aren't in the updates
      this._removeInaccessibleCustomization(updates.customizationMap);

  	// Change latest version for withdrawn installed lower version apps/Customizations
  	this._fixLatestVersionForWithdrawnInstalledApps(updates.appMap);
  	this._fixLatestVersionForWithdrawnInstalledCustomizations(updates.customizationMap);

      // Delete versions from existing sys_app_version/sys_app_customization_version if they aren't in the updates
      this._removeWithdrawnAppVersions(updates.appMap);
      this._removeWithdrawnCustomizationVersions(updates.customizationMap);

      // Update app records in sys_store_app, sys_remote_app and sys_app_version tables
      this._updateAppRecords(updates.appMap);
      // Update app records in sys_app_customization and sys_app_customization_version tables
      this._updateCustomizations(updates.customizationMap);

      // update logos async
      this._downloadLogosAsync();

      this._countAndSendPushNotification(updates.appMap);

      this.refreshStoreResponseCache(args);
      //trigger product sync
      gs.eventQueue('sn_appclient.products_data_sync');
  },

  refreshStoreResponseCache: function(args) {
  	if(!this.appManagerCacheEnabled)
  		return;
  	if (gs.nil(args)){
  		args = {
  			"sharedInternally" : false,
  			"forceUpdate" : false,
  			"isMaint" : false
  		}
  	}
  	// prevent undefines to throw null pointer exceptions
  	var sharedInternally = !!args.sharedInternally;
  	var forceUpdate = !!args.forceUpdate;
  	var isMaint = !!args.isMaint;
  	var cacheLastUpdatedTime = new global.AppManagerCache().getCacheLastUpdatedTime({key: "storeResponse", sharedInternally: sharedInternally});
  	this.registerDbUpdatesForLatestUpdatedSysStoreApps(cacheLastUpdatedTime); // this is done so that cache gets updated after CI/CD install
  	this.registerDbUpdatesForLatestUpdatedSysCustomizations(cacheLastUpdatedTime); // this is done so that cache gets updated after customization publish
  	new AppsData(isMaint).putStoreResponseInAppManagerCache(sharedInternally, this.appsUpdatedInDB, forceUpdate);
  	if(forceUpdate) {
  		new AppsData(isMaint).putStoreResponseInAppManagerCache(!sharedInternally, this.appsUpdatedInDB, forceUpdate);
  	}
  },

  registerDbUpdatesForLatestUpdatedSysStoreApps: function(cacheLastUpdatedTime) {
  	var storeAppGr = new GlideRecord("sys_store_app");
  	storeAppGr.addQuery("sys_updated_on", ">=", cacheLastUpdatedTime);
  	storeAppGr.query();
  	while (storeAppGr.next()) {
  		this._registerDbUpdates(storeAppGr.getUniqueValue());
  	}
  },

  registerDbUpdatesForLatestUpdatedSysCustomizations: function(cacheLastUpdatedTime) {
  	var custGr = new GlideRecord("sys_app_customization");
  	custGr.addQuery("sys_updated_on", ">=", cacheLastUpdatedTime);
  	custGr.query();
  	while (custGr.next()) {
  		this._registerDbUpdates(custGr.vendor_app.toString());
  	}
  },

  deleteAppManagerCache: function() {
  	if(this.appManagerCacheEnabled)
  		new global.AppManagerCache().deleteAllCache();
  },

  _updateAppRecords: function(appMap) {
      for(var prop in appMap) {
          var update = appMap[prop];
          var app = this._getApp(update.source_app_id);

          this._setRelevantVersion(update, app);
          this._filterAndUpdateAppRecords(update, app);

          if (this.isInstanceOffline && update[update.relevant_version].hasOwnProperty('logo_hash') && update[update.relevant_version].logo_hash != this.constants.NO_LOGO_EXIST)
              this.downloadLogoList.push(update);
      }
  },

  _updateCustomizations: function(customizationMap) {
      for (var prop in customizationMap) {
          var update = customizationMap[prop];
          var customization = this._getCustomization(update.source_customization_id);

          this._getLatestVersionForCustomization(update);
          this._filterAndUpdateCustomizations(update, customization);
      }
  },

  _fetchAppUpdates: function(isInteractive, honourChecksum, cacheArgs, checkUpdateMutex) {
  	var isMutexAvailable = sn_app_api.AppStoreAPI.isUpdateMutexAvailable();
  	if(gs.isPaused() || (checkUpdateMutex && !isMutexAvailable)) {
  		gs.info("checkAvailableUpdates returning without calling repo as another operation is in progress");
  		return;
  	}
  	if (gs.getProperty("sn_appclient.client_calls_allowed", "true") == "false" && !this.isInstanceOffline) {
  		gs.info("checkAvailableUpdates returning without calling repo as client_calls_allowed is false");
  		return;
  	}

  	var payload = !this.isInstanceOffline ? this._getAvailableStoreUpdates(honourChecksum, cacheArgs) : this._getOfflineUpdates();
  	this.canSendNotification = !isInteractive && payload.opstatus == 2 && this.canSendNotification;

  	// opstatus indicates the payload has changed from the last update
  	if(payload.opstatus == 0)
  		return;

      return payload.data;
  },
  
  /**
   * Retrieves available updates for this instance from the central app repository
   */
  _getAvailableStoreUpdates : /*[sys_store_app,...]*/ function(honourChecksum, cacheArgs) {
  	var gu = new GlideUpgradeUtil();
  	var installedAppsJson = this._getInstalledApplicationsJSON();
  	var installedCustomizationsJSON = this._getInstalledCustomizationsJSON();
  	var parms = {
  		glide_build: gu.getCurrentBuild(),
  		glide_build_name: gu.getCurrentBuildName(),
  		applications: installedAppsJson,
          		customizations: installedCustomizationsJSON,
  		plugin_activation_enforcement_mode: this._getPluginActivationEnforcementMode(),
  		is_instance_licensing_aware: this._getIsInstanceLicenseAware()
  	};
  	var scopedAppRepoRequestObj = new ScopedAppRepoRequest("get_all_available_apps");
  	var candidateUpdateResponse = scopedAppRepoRequestObj
  				//.addHeader("Accept-Encoding", "gzip, deflate, br")
  				.setParameter("send_logo_sys_id", true)
  				.setParameter("glide_build", parms.glide_build)
  				.setParameter("glide_build_name", parms.glide_build_name)				
  				.setParameter("applications", parms.applications)
  				.setParameter("customizations", parms.customizations)
  				.setParameter("plugin_activation_enforcement_mode", parms.plugin_activation_enforcement_mode)
  				.setParameter("is_instance_licensing_aware", parms.is_instance_licensing_aware)
  				.setParameter("schedule_install_check",this.checkAppEntitlements)
  				.setParameter("instance_checksum", gs.getProperty("sn_appclient.last.processed.checksum"))
  				.setParameter("specific_apps_to_update", this.specificAppsToUpdate)
  				.setQuiet(this._quiet)
  				.post();
  	var candidateUpdates = [];
  	var currentChecksum = gs.getProperty("sn_appclient.last.processed.checksum");
  	var calculatedChecksum = new GlideDigest().md5_digest(candidateUpdateResponse + installedAppsJson + installedCustomizationsJSON);
  	var checksumModified = currentChecksum != calculatedChecksum;
  	var errorMessage = scopedAppRepoRequestObj.getErrorMessage();
  	var statusCode = scopedAppRepoRequestObj.getStatusCode();
  	this._updateLatestErrorMessage(errorMessage, statusCode);

  	if ((honourChecksum && !checksumModified) || statusCode == 401 || statusCode == 412) {
  		if (!errorMessage)
  			errorMessage = "Ignoring updates as there is no difference in payload from last received one";

  		gs.debug(errorMessage);

  		return {
  			opstatus : 0,
  			data : candidateUpdates
  		};
  	} else if (statusCode == 0 || statusCode == 408 || statusCode == 429) {
  	    // Status Code = 0 will most likely will be a socket timeout - so handle as timeout
  	    // Status Code 408 = Request Timeout
  	    // Status Code 429 = Too Many Requests
  	    if (statusCode == 429)
  	        errorMessage = "Too Many Requests (429)";
  	    else
  	        errorMessage = "Request Timeout (408)";

  	    cacheArgs.forceUpdate = true;
  	    this.refreshStoreResponseCache(cacheArgs);
  	    throw new Error(errorMessage);
  	}

  	gs.eventQueue("sn_appclient.update.system.property", null, "sn_appclient.last.processed.checksum", calculatedChecksum);
  	try {
  		//if talking to localhost response will be 200-OK but content is Page not Found
  		candidateUpdates = this.json.decode(candidateUpdateResponse);
  	} catch (e) {
  		gs.error("Unable to parse response from App Repo. The server may not be active >> {0}", candidateUpdateResponse);
  	}
  	
  	if (null == candidateUpdates || typeof candidateUpdates.push !=='function')
  		candidateUpdates = [];
  	
  	gs.debug("Got ({0}) candidate updates: {1}", candidateUpdates.length, this.json.encode(candidateUpdates));

  	gs.setProperty("sn_appclient.apps_last_sync_time", new GlideDateTime());
  	
  	return {
  		opstatus : checksumModified ? 2 : 1,
  		data : candidateUpdates
  	};
  },
  
  updateInstalledAppsInStore: function() {
  	var installedAppsJson = this._getInstalledApplicationsJSON();
  	var installedCustomizationsJSON = this._getInstalledCustomizationsJSON();
  	var parms = {
  		applications: installedAppsJson,
  		customizations: installedCustomizationsJSON,
  	};

  	var scopedAppRepoRequestObj = new ScopedAppRepoRequest("update_installed_apps");
  	var response = scopedAppRepoRequestObj
  				.setParameter("applications", parms.applications)
  				.setParameter("customizations", parms.customizations)
  				.setQuiet(this._quiet)
  				.post();
  	gs.info("Response for update_installed_apps: " + response);
  },

  updateInstalledAppsInStoreAsync: function() {
  	var worker = new GlideScriptedHierarchicalWorker();
  	worker.setProgressName("Updating Installed Apps In Store");
  	worker.setBackground(true);
  	worker.setCannotCancel(true);
  	worker.setScriptIncludeName("sn_appclient.UpdateChecker");
  	worker.setScriptIncludeMethod("updateInstalledAppsInStore");
  	worker.start();
  	return worker.getProgressID();
  },

  _updateLatestErrorMessage: function(errorMessage, statusCode) {
  	if(!errorMessage || statusCode !== 412)
  		errorMessage = "";
  	var gr = new GlideRecord("sn_appclient_store_outbound_http_quota");
  	gr.get("request_type", "get_all_available_apps");
  	if (gr.isValidRecord()) {
  		gr.last_request_error_message = errorMessage;
  		gr.update();
  	}
  },

  _downloadLogosAsync: function() {
  	if(this.isInstanceOffline)
  		gs.eventQueue('sn_appclient.update.logos', null, JSON.stringify(this.downloadLogoList));
  },

  /**
   * Downloads the logos for all apps
   * Called by script action UpdateLogos
   */
  downloadLogos: function(newestLogoVersions) {
  	var logoDownloader = new LogoDownloader();
  	gs.info("Updating logo for: {0}", this.json.encode(newestLogoVersions));
  	logoDownloader.downloadLogoForApps(newestLogoVersions);
  },

  refreshChecksum: function() {
  	gs.info("Refreshing sn_appclient.last.processed.checksum");
  	var props = {};
  	props["sn_appclient.last.processed.checksum"] = "";
  	new GlideAppLoader().setAuthorProperties(props);
  },

  _getUpdates: function(data) {
  	return {
  	    appMap : this._getUpdatesMap(data),
  	    customizationMap : this._getCustomizationMap(data)
  	};
  },

  _getUpdatesMap: function(updates) {
  	var updatesMap = {};
  	if(!updates || updates.length <= 0)
  		return updatesMap;

  	for (var i = 0; i < updates.length; i++) {
  		var update = updates[i];
  		if(!this._isAppFieldsValid(update))
  			continue;

  		updatesMap[update.source_app_id] = update;

  		for (var j = 0; j < update.versions.length; j++) {
  			var versionObj = update.versions[j];
  			updatesMap[update.source_app_id][versionObj.version] = versionObj;
  		}
  		delete updatesMap[update.source_app_id].versions;

  	}
  	return updatesMap;
  },

  _getCustomizationMap: function(updates) {
  	var customizationMap = {};
  	for (var i = 0; i < updates.length; i++) {
  		var update = updates[i];
  		if (!this._isCustomizationFieldsValid(update))
  			continue;

  		var customizations = update.customizations;
  		customizations.scope = update.scope;
  		customizations.app_id = update.source_app_id;
  		customizationMap[update.source_app_id] = customizations;

  		for (var j = 0; j < customizations.versions.length; j++) {
  			var version = customizations.versions[j];
  			customizationMap[update.source_app_id][version.customization_version] = version;
  		}
  		delete customizationMap[update.source_app_id].customizations;
  	}

  	return customizationMap;
  },

  _getDeltaCustomizationMap: function(payload) {
  	var customizationMap = {};

  	var custGr = new GlideRecord("sys_app_customization");
  	custGr.query();

  	var loadedMap = {};
  	while (custGr.next()) {
  		if (!loadedMap[custGr.getUniqueValue()]) loadedMap[custGr.getUniqueValue()] = {};
  		loadedMap[custGr.getUniqueValue()].id = custGr.getUniqueValue();
  		loadedMap[custGr.getUniqueValue()].installed = custGr.version.toString();
  		loadedMap[custGr.getUniqueValue()].processed = false;
  	}

  	var verGr = new GlideRecord("sys_app_customization_version");
  	verGr.query();

  	while (verGr.next()) {
  		if (!loadedMap[verGr.app_customization]) loadedMap[verGr.app_customization] = {};
  		if (!loadedMap[verGr.app_customization][verGr.version]) loadedMap[verGr.app_customization][verGr.version] = {};
  	}

  	for (var i = 0; i < payload.length; i++) {
  		var changes = false;

  		var app = payload[i];
  		if (!this._isCustomizationFieldsValid(app))
  			continue;

  		var customizations = app.customizations;
  		customizations.scope = app.scope;
  		customizations.app_id = app.source_app_id;

  		var installed = "none";
  		if (!loadedMap.hasOwnProperty(customizations.source_customization_id))
  			changes = true;
  		else
  			installed = loadedMap[customizations.source_customization_id].installed;

  		for (var j = 0; j < customizations.versions.length; j++) {
  			var version = customizations.versions[j];
  			if (installed != "none") {
  				if (!this.versionComparator.isUpgrade(installed, version.customization_version))
  					continue;
  			}

  			if (!changes && !loadedMap[customizations.source_customization_id][version.customization_version])
  				changes = true;

  			customizations[version.customization_version] = version;
  		}

  		if (changes)
  			customizationMap[app.source_app_id] = customizations;

          // Done processing - remove from loaded map to mark that it was found
          if (loadedMap.hasOwnProperty(customizations.source_customization_id))
              loadedMap[customizations.source_customization_id].processed = true;

  		delete customizations.customizations;
  		delete app.customizations;
  	}

  	if(this.specificAppsToUpdate !== "[]")
  		return customizationMap;
  	
      // Remove all the customizations that were not in the payload
      for (var key in loadedMap) {
          var cust = loadedMap[key];

          if (!cust.processed) {
              gs.info("Removing Customization: " + cust.id);

              verGr = new GlideRecord("sys_app_customization_version");
              verGr.addQuery("app_customization", cust.id);
              verGr.query();
              if (verGr.getRowCount() > 0)
                  verGr.deleteMultiple();

              custGr = new GlideRecord("sys_app_customization");
              custGr.get(cust.id);
              if (custGr.isValidRecord()) {
                  var ver = custGr.getValue("version");
                  if ((gs.nil(ver) || ver == "none"))
                      custGr.deleteRecord();
              }
          }
      }

  	return customizationMap;
  },

  _countAndSendPushNotification: function(updatesMap) {
      if(this.canSendNotification){
          this._checkIndicatorsCountForInstalledAppsWithNoStoreData(updatesMap);

          Object.keys(this.appNotificationConfig).forEach(function(item){
              this.totalAppNotificationCount += this.appNotificationConfig[item]["count"];
          }, this);

          if(this.totalAppNotificationCount > 0)
              this._sendPushNotification();
      }
  },

  _isAppFieldsValid: function(app) {
  	var requiredFields = ["scope", "source_app_id", "versions"];
  	var invalidFields = [];
  	requiredFields.forEach(function(key) {
  		if(!app[key])
  			invalidFields.push(key);
  	});

  	if(invalidFields.length > 0) {
  		gs.debug("Removing app {0} from the list as the required fields {1} are missing.",[app.app_name, invalidFields.join()]);
  		return false;
  	}
  	return true;
  },

  _isCustomizationFieldsValid: function(app) {
  	var requiredFields = ["scope", "source_app_id", "customizations"];
  	var invalidFields = [];
  	requiredFields.forEach(function(key) {
  		if (!app[key])
  			invalidFields.push(key);
  	});

      if (invalidFields.length == 0) {
          var customizations = app.customizations;
          requiredFields = ["source_customization_id", "versions"];
          requiredFields.forEach(function(key) {
              if (!customizations[key])
                  invalidFields.push(key);
          });
      }

  	if (invalidFields.length > 0) {
  		gs.debug("Removing customizations {0} from the list as the required fields {1} are missing.", [app.app_name, invalidFields.join()]);
  		return false;
  	}

  	return true;
  },

  _getInstalledApplicationsJSON: function() {
  	var applications = new GlideRecord("sys_scope");
  	applications.addQuery("sys_class_name", "IN", "sys_store_app,sys_app");
  	applications.addQuery("sys_id", "!=", "global");
  	applications.query();
  	var appRay = [];
  	var sysStoreAppGr = new GlideRecord("sys_store_app");
  	var last_installed_from;
  	var installation_info;

  	while (applications.next()) {
  		last_installed_from = "";
  		if(applications.sys_class_name == "sys_store_app" && sysStoreAppGr.get(applications.sys_id)) {
  			last_installed_from = sysStoreAppGr.last_installed_from;
  			installation_info = sysStoreAppGr.getValue("installation_info");
  		}

  		var app = {
  			source_app_id: applications.sys_id.toString(),
  			source: applications.source.toString(),
  			scope: applications.scope.toString(),
  			active: applications.active.toString(),
  			version: applications.version.toString(),
  			vendor: applications.vendor.toString(),
  			vendor_prefix: applications.vendor_prefix.toString(),
  			sys_class_name: applications.sys_class_name.toString(),
  			name: applications.name.toString(),
  			scoped_administration: applications.scoped_administration.toString(),
  			last_installed_from: last_installed_from.toString(),
  			installation_info : installation_info
  		};
  		appRay.push(app);
  	}
  	var text = this.json.encode(appRay);
  	gs.debug("installedAppsJson: " + text);
  	return text;
  },

  _getInstalledCustomizationsJSON: function() {
  	var customizations = new GlideRecord("sys_app_customization");
  	customizations.addQuery("version", "!=", "none");
  	customizations.query();

  	var results = [];
  	while (customizations.next()) {
  		var customization = {
  			source_app_id: customizations.sys_id.toString(),
  			source: customizations.vendor_app.scope.toString(),
  			scope: customizations.vendor_app.scope.toString(),
  			active: customizations.vendor_app.active.toString(),
  			version: customizations.version.toString(),
  			sys_class_name: customizations.getTableName(),
  			installation_info : customizations.vendor_app.installation_info.toString() || "{}",
  			base_app_version : customizations.vendor_app_version.toString()
  		};
  		results.push(customization);
  	}

      gs.info("Installed Customizations: " + this.json.encode(results));
  	return this.json.encode(results);
  },

  /*
  * Deletes sys_remote_app records, if the records sys_id isn't provided as one of the
  * apps sent by the store
  */
  _removeInaccessibleRemoteApps: /*void*/ function(/*array of hash*/accessible) {
  	gs.debug("Removing all inaccessible apps");
  	
  	var allRemoteApps = new GlideRecord("sys_remote_app");
  	allRemoteApps.query();
  	while (allRemoteApps.next()) {
  		var candidate_app_id = allRemoteApps.sys_id.toString();
  		var candidate_scope = allRemoteApps.scope.toString();
  		if (typeof accessible[candidate_app_id] == 'undefined') {
  			gs.info("Removing inaccessible sys_remote_app: {0} ({1})", candidate_scope, candidate_app_id);
  			this._registerDbUpdates(allRemoteApps.getUniqueValue());
  			allRemoteApps.deleteRecord();
  		}
  	}
  },
  
  /*
   * Deletes sys_app_customization records, if the records vendor_app isn't provided as one of the
   * apps sent by the store and customization is not installed
   */
  _removeInaccessibleCustomization: /*void*/ function(/*array of hash*/ accessible) {
  	gs.debug("Removing all inaccessible customizations");

  	var gr = new GlideRecord("sys_app_customization");
  	gr.query();
  	while (gr.next()) {
  		var base_id = gr.getValue("vendor_app");
  		var ver = gr.getValue("version");

  		gs.info("Checking Customization: {0} - {1} / {2}", base_id, ver, accessible.hasOwnProperty(base_id));

  		this._registerDbUpdates(gr.vendor_app);
  		if (gs.nil(base_id)) {
  			gs.info("Removing sys_app_customization with null base app");
  			gr.deleteRecord();
  		} else if (!accessible.hasOwnProperty(base_id) && (gs.nil(ver) || ver == "none")) {
  			gs.info("Removing inaccessible sys_app_customization: {0}", base_id);
  			gr.deleteRecord();
  		}
  	}
  },

  _fixLatestVersionForWithdrawnInstalledApps: function(updatesMap) {
  	var storeAppGr = new GlideRecord("sys_store_app");
  	storeAppGr.addActiveQuery();
  	storeAppGr.query();

  	while(storeAppGr.next()) {
  		var storeAppVersion = storeAppGr.getValue("version");
  		var storeAppLatestVersion = storeAppGr.getValue("latest_version");
  		if(gs.nil(updatesMap[storeAppGr.getValue("sys_id")]) && !this.versionComparator.isEqual(storeAppVersion, storeAppLatestVersion)) {
  			storeAppGr.setValue("latest_version", storeAppVersion);
  			storeAppGr.update();
  			this._registerDbUpdates(storeAppGr.getUniqueValue());
  		}
  	}

  },

  _fixLatestVersionForWithdrawnInstalledCustomizations: function(customizationMap) {
  	var customization = new GlideRecord("sys_app_customization");
  	customization.query();

  	while (customization.next()) {
  		var version = customization.getValue("version");
  		var latest = customization.getValue("latest_version");
  		if (gs.nil(customizationMap[customization.getValue("sys_id")]) && !this.versionComparator.isEqual(version, latest)) {
  			customization.setValue("latest_version", version);
  			customization.update();
  			this._registerDbUpdates(customization.vendor_app);
  		}
  	}
  },

  _filterLocalApps: /*array*/function (/*array of hashes*/ updatesMap) {
  	var localAppsGr = new GlideRecord("sys_app");
  	localAppsGr.query();

  	while(localAppsGr.next()) {
  		var candidate_app_id = localAppsGr.sys_id.toString();
  		if (!gs.nil(updatesMap[candidate_app_id])) {
  			gs.info("Removing local app {0} from the list", updatesMap[candidate_app_id].app_name);
  			delete updatesMap[candidate_app_id];
  		}
  	}

  },
  
  _filterAndUpdateAppRecords: function(update, /*[sys_store_app/sys_remote_app]*/app) {
  	var propertiesToCompare = ["latest_version", "dependencies", "price_type", "lob", "indicators", "display_message", "compatibilities",
  		"short_description", "name", "is_store_app", "block_install", "block_message", "upload_info", "needs_app_engine_licensing",
          "custom_table_count", "products", "store_latest_updated_time", "repo_latest_updated_time", "logo"];

  	gs.debug("Checking app update {0}:{1} ({2})", update.scope, update.relevant_version, update.source_app_id);
  	var includeUpdate = false;
  	var includeReason = null;

  	if (app === null) {
  		includeUpdate = true;
  		includeReason = "New app";
  		app = this._createRemoteApp(update);
  	} else if (!this._areEqualBoolean(app.shared_internally, update.shared_internally ? update.shared_internally : false)) {
  		includeUpdate = true;
  		includeReason = "Shared internally";
  	} else if (app.isValidField('auto_update') &&
  		update.hasOwnProperty('auto_update') &&
  		!this._areEqualBoolean(app.auto_update, update.auto_update)) {
  		includeUpdate = true;
  		includeReason = "Auto update change";
  	} else if (app.isValidField('uninstall_blocked') &&
  		update.hasOwnProperty('uninstall_blocked') &&
  		!this._areEqualBoolean(app.uninstall_blocked, update.uninstall_blocked)) {
  		includeUpdate = true;
  		includeReason = "Uninstall blocked change";
  	} else if (app.isValidField('hide_on_ui') &&
  		update.hasOwnProperty('hide_on_ui') &&
  		!this._areEqualBoolean(app.hide_on_ui, update.hide_on_ui)) {
  		includeUpdate = true;
  		includeReason = "Hidden on ui change";
  	} else if (app.isValidField('installed_as_dependency') &&
  		update.hasOwnProperty('installed_as_dependency') &&
  		!this._areEqualBoolean(app.installed_as_dependency, update.installed_as_dependency)) {
  		includeUpdate = true;
  		includeReason = "Installed as dependency change";
  	}

  	if (!includeUpdate) {
  		for (var i = 0; i < propertiesToCompare.length; i++) {
  			var propertyName = propertiesToCompare[i];
  			if(!app.isValidField(propertyName)) continue;

  			var appPropertyValue = app[propertyName];
  			var updatePropertyValue = update[propertyName] || update[update.relevant_version][propertyName];

  			if (propertyName == "lob" || propertyName == "indicators" || propertyName == "products")
  				updatePropertyValue = updatePropertyValue ? JSON.stringify(updatePropertyValue) : "[]";

  			if (propertyName == "name")
  				updatePropertyValue = update.app_name;
  			
  			if(propertyName == "logo")
  				updatePropertyValue = update[update.relevant_version]["logo_sys_id"];

  			if (!this._areEqual(appPropertyValue, updatePropertyValue)) {
  				includeUpdate = true;
  				includeReason = "Property change: " + propertyName;
  				break;
  			}
  		}
  		if( app.getRecordClassName() == "sys_store_app" && this.versionComparator.isUpgrade(app.version, update.latest_version) ){
  			includeUpdate = true;
  		}
  	}

  	if (includeUpdate) {
  		// update sys_store_app / sys_remote_app record
  		gs.debug("Including app update {0}:{1} ({2}) - {3}", update.source_app_id, update.relevant_version, update.scope, includeReason);
  		this._updateLatestVersionFields(update, app);
  	}

  	// update sys_app_version record
  	this._refreshVersions(app, update);
  },

  _filterAndUpdateCustomizations: function(update, /*sys_app_customization*/ customization) {
  	gs.debug("Checking customization update {0}: {1}", update.scope, update.app_id);

  	if (customization === null) {
  		customization = this._createCustomization(update);
  	} else if (customization.getValue("latest_version") != update.latest_version) {
  		var vendorApp = this._getApp(customization.getValue("vendor_app"));
  		if(vendorApp != null) {
  			if(vendorApp.getRecordClassName() == "sys_store_app" && this.versionComparator.isUpgrade(customization.getValue("latest_version"),update.latest_version))
  				vendorApp.setValue("update_available", true);
  			vendorApp.setValue("customization", customization.getValue("sys_id"));
  			vendorApp.update();
  		}
  		customization.setValue("latest_version", update.latest_version);
  		customization.update();
  		this._registerDbUpdates(customization.vendor_app);
  	}

  	// update sys_app_customization_version record
  	this._refreshCustomizationVersions(customization, update);
  },

  _isDowngrade: /*boolean */ function(/*from version*/ currentVersion, /* to version */ targetVersion) {
  	return this.versionComparator.isDowngrade(currentVersion, targetVersion);
  },

  _updateLatestVersionFields: /*boolean*/ function(/*Hash*/ update, app) {
  	if (app.getRecordClassName() == "sys_store_app" && !this._areEqual(app.version, update.relevant_version)){
  		gs.info("No need to update values, local app is a different version. Updating only relevant fields.");
  		app.setValue("is_store_app", update.is_store_app);
  		app.setValue("update_available", this.versionComparator.isUpgrade(app.version, update.relevant_version));
  		app.setValue("auto_update", update.auto_update);
  		app.setValue("latest_version", update.latest_version);
  		app.setValue("products", JSON.stringify(update.products || []));
  		app.setValue("shared_internally", update.shared_internally ? update.shared_internally : false);
  		app.setValue("uninstall_blocked", update.uninstall_blocked);
  		app.setValue("hide_on_ui", update.hide_on_ui);
  		app.setValue("installed_as_dependency", update.installed_as_dependency);
  		if(!this.isInstanceOffline)
  			app.setValue("logo", update[update.relevant_version]["logo_sys_id"]);
  		app.update();
  		this._registerDbUpdates(app.getUniqueValue());
  		return;
  	}
  	this._updateApp(app, update);
  },

  _updateApp: /*void*/ function(/*GlideRecord sys_remote_app | sys_store_app*/ app, /*{}*/ update) {
  	app.setValue("latest_version", update.latest_version);
  	app.setValue("needs_app_engine_licensing", update[update.relevant_version].needs_app_engine_licensing);
  	app.setValue("custom_table_count", update[update.relevant_version].custom_table_count);
  	app.setValue("name", update.app_name);
  	app.setValue("dependencies", update[update.relevant_version].dependencies);
  	if (update.short_description)
  		app.setValue("short_description", update[update.relevant_version].short_description);
      app.setValue("compatibilities", update[update.relevant_version].compatibilities);
      app.setValue("is_store_app", update[update.relevant_version].is_store_app);
      app.setValue("shared_internally", update.shared_internally ? update.shared_internally : false);
  	app.setValue("price_type", update[update.relevant_version].price_type ? update[update.relevant_version].price_type : "");
  	app.setValue("lob", (JSON.stringify(update[update.relevant_version].lob) || '[]'));
  	app.setValue("indicators", (JSON.stringify(update[update.relevant_version].indicators) || '[]'));
  	app.setValue("display_message", update[update.relevant_version].display_message ? update[update.relevant_version].display_message : "");
  	app.setValue("upload_info", update[update.relevant_version].upload_info ? update[update.relevant_version].upload_info : "");
  	app.setValue("products", JSON.stringify(update.products || []));
  	if (app.isValidField("auto_update"))
  		app.setValue("auto_update", update.auto_update);
      if (app.isValidField("block_install"))
          app.setValue("block_install", update[update.relevant_version].block_install);
      if (app.isValidField("block_message"))
          app.setValue("block_message", update[update.relevant_version].block_message);
  	if (app.isValidField("delivery_source"))
  		if(update[update.relevant_version].delivery_source)
  			app.setValue("delivery_source",update[update.relevant_version].delivery_source);
  		else
  			app.setValue("delivery_source",'store');
  	if (app.isValidField("store_latest_updated_time"))
  		app.setValue("store_latest_updated_time", update[update.relevant_version]["store_latest_updated_time"]);
  	if (app.isValidField("repo_latest_updated_time"))
  		app.setValue("repo_latest_updated_time", update[update.relevant_version]["repo_latest_updated_time"]);
  	app.setValue("uninstall_blocked", update.uninstall_blocked);
  	app.setValue("hide_on_ui",update.hide_on_ui);
  	app.setValue("installed_as_dependency", update.installed_as_dependency);
  	if(!this.isInstanceOffline)
  		app.setValue("logo", update[update.relevant_version]["logo_sys_id"]);
  	app.setWorkflow(true); // ensure workflow is enabled
  	if (app.getRecordClassName() == "sys_store_app")
  	    app.setValue("update_available", this.versionComparator.isUpgrade(app.version, update.latest_version));
  	app.update();
  	this._registerDbUpdates(app.getUniqueValue());
  },

  _createRemoteApp: /*gliderecord*/ function(/*{sys_id,name,scope,version,description}*/ update) {
  	gs.info("Creating sys_remote_app for: {0}", this.json.encode(update));
  	var gr = new GlideRecord("sys_remote_app");
  	gr.initialize();
  	
  	if (update.hasOwnProperty("source_app_id") && update.source_app_id) {
  		gr.setValue("source_app_id", update.source_app_id);
  		gr.setNewGuidValue(update.source_app_id);

  		if (!update[update.latest_version]) {
  			update[update.latest_version] = this._getAppVersionDetails(update.source_app_id, update.latest_version);
  		}
  	}
  	
  	gr.setValue("name", update.app_name);
  	gr.setValue("scope", update.scope);
  	gr.setValue("latest_version", update.latest_version || update.relevant_version);
  	gr.setValue("vendor", update.vendor);
  	if(update[update.latest_version].short_description)
  		gr.setValue("short_description", update[update.latest_version].short_description);
  	gr.setValue("dependencies", update[update.latest_version].dependencies);
  	gr.setValue("compatibilities", update[update.latest_version].compatibilities);
  	gr.setValue("is_store_app", update[update.latest_version].is_store_app);
  	gr.setValue("block_install", update[update.latest_version].block_install);
  	gr.setValue("block_message", update[update.latest_version].block_message);
  	gr.setValue("is_offline_app",update.is_offline_app);
  	gr.setValue("needs_app_engine_licensing", update[update.latest_version].needs_app_engine_licensing);
  	gr.setValue("custom_table_count", update[update.latest_version].custom_table_count);
  	
  	if (gr.isValidField('auto_update'))
  		gr.setValue("auto_update", update.auto_update);

  	if (update[update.latest_version].has_demo_data == "true")
  		gr.setValue("demo_data", "has_demo_data");
  	else
  		gr.setValue("demo_data", "no_demo_data");		
  	
  	if (gr.isValidField('publish_date'))
  		gr.setValue("publish_date", update[update.latest_version].publish_date);
  	if(gr.isValidField("sys_code"))
  		gr.setValue("sys_code",update.sys_code);
  	gr.setValue("uninstall_blocked",update.uninstall_blocked);
  	gr.setValue("hide_on_ui",update.hide_on_ui);
  	gr.setValue("installed_as_dependency", update.installed_as_dependency);
  	if(!this.isInstanceOffline)
  		gr.setValue("logo", update[update.relevant_version]["logo_sys_id"]);
  	gr.insert();
  	this._registerDbUpdates(gr.getUniqueValue());
  	return gr;
  },
  
  _createCustomization: /*gliderecord*/ function(update) {
  	gs.info("Creating sys_app_customization for: {0}", this.json.encode(update));
  	var gr = new GlideRecord("sys_app_customization");
  	gr.initialize();

  	if (update.hasOwnProperty("source_customization_id") && update.source_customization_id)
  		gr.setNewGuidValue(update.source_customization_id);

  	gr.setValue("version", "none");
  	gr.setValue("vendor_app", update.app_id);
  	gr.setValue("latest_version", update.latest_version);

  	gr.insert();
  	var vendorApp = this._getApp(update.app_id);
  	if(vendorApp != null) {
  		vendorApp.setValue("customization", gr.getValue("sys_id"));
  		if(vendorApp.getRecordClassName() == "sys_store_app")
  				vendorApp.setValue("update_available", true);
  		vendorApp.update();
  	}
  	return gr;
  },

  _constructConnectMessage: function(filters, linkText, count) {
  	var filterString = '';
  	var lengthOfFilters = filters.length -1;
  	for(var filterCategory = 0;  filterCategory < filters.length; filterCategory++){
  		filterString += filters[filterCategory].category + "=" + filters[filterCategory].id;
  		if(filterCategory <  filters.length -1)
  			filterString += "^";
  	}

              return "[code]<a href='/nav_to.do?uri=/$allappsmgmt.do?sysparm_filter=" + filterString + "'>" + linkText + "<a>[/code]" + " (" + count + ")\n";
  },

  //Send connect chat messages to all admin users only
  _sendPushNotification: function() {
  	var groupName = this.NOTIFICATION_GROUP_NAME;
  	var groupGr = new GlideRecord("live_group_profile");
  	groupGr.addQuery("name", groupName);
  	groupGr.query();
  	if (!groupGr.next()) {
  		groupGr.setValue("name", groupName);
  		groupGr.setValue("public_group", false);
  		groupGr.setValue("visible_group", false);
  		groupGr.setValue("type", "connect");
  		groupGr.insert();
  	}
  	var collaborationManager = sn_connect.Conversation.get(groupGr.sys_id);
  	var users = new GlideRecord('sys_user_has_role');
  	users.addQuery('role', '2831a114c611228501d4ea6c309d626d');
  	users.addQuery('user.active', true);
  	users.query();
  	while (users.next()) {
  		collaborationManager.addSubscriber(users.user, true);
  	}

  	var connectMessage = gs.getMessage("Below store apps require attention:") + "\n";

  	Object.keys(this.appNotificationConfig).forEach(function(item){
  		if(this.appNotificationConfig[item]["count"] > 0)
  			connectMessage += this._constructConnectMessage(this.appNotificationConfig[item]["filter"], this.appNotificationConfig[item]["linkText"], this.appNotificationConfig[item]["count"]);
  	},this);

  	collaborationManager.sendMessage(connectMessage);
  },

  _checkIndicatorsCountForInstalledAppsWithNoStoreData: function(updatesMap) {
  	var app = new GlideRecord('sys_store_app');
  	app.addActiveQuery();
  	app.addNotNullQuery('version');
  	app.addQuery("sys_id", "NOT IN", updatesMap);
  	app.addQuery("indicators", "CONTAINS", "trial_deactivation_requested");
  	app.query();
              var rowCountForDeactivatedApps = app.getRowCount();
  	this.appNotificationConfig["appDeactivationRequests"]["count"] += rowCountForDeactivatedApps;

  	var gr = new GlideRecord('sys_store_app');
  	gr.addActiveQuery();
  	gr.addNotNullQuery('version');
  	gr.addQuery("sys_id", "NOT IN", updatesMap);
  	gr.addQuery("indicators", "CONTAINS", "entitlement_revoked");
  	gr.query();
              var rowCountForEntRevokedApps = app.getRowCount();
  	this.appNotificationConfig["appEntitlementRevoked"]["count"] += rowCountForEntRevokedApps;
  },
  
  _checkAndUpdatePushNotificationCount: function(versionObject, parentAppObject, notificationTracker) {
  	if(versionObject.indicators) {
  		for(var i = 0; i < versionObject.indicators.length; i++) {
  			if(versionObject.indicators[i].id == this.ENTITLEMENT_REVOKED)
  				this.appNotificationConfig["appEntitlementRevoked"]["count"]++;
  			if(versionObject.indicators[i].id == this.TRIAL_DEACTIVATION_REQUESTED)
  				this.appNotificationConfig["appDeactivationRequests"]["count"]++;
  		}
  	}
  	if((parentAppObject.getRecordClassName() == "sys_store_app") && this.versionComparator.isUpgrade(parentAppObject.getValue("version"), versionObject.version) && !notificationTracker.isUpdateAvailableChecked) {
  		this.appNotificationConfig["appAvailableForUpdate"]["count"]++;
  		notificationTracker.isUpdateAvailableChecked = true;
  	}
  		
  	if(parentAppObject.getRecordClassName() == "sys_remote_app" && parentAppObject.shared_internally == false && !notificationTracker.isInstallationAvailableChecked) {
  		this.appNotificationConfig["appAvailableForInstall"]["count"]++;
  		notificationTracker.isInstallationAvailableChecked = true;
  	}
  },
  
  _refreshVersions : /*void*/ function(/*GlideRecord sys_store_app | sys_remote_app */app, /*{}*/ update) {
  	//update is the app object which has all the version info
  	var appField = (app.getRecordClassName() == "sys_store_app")  ? "store_application" : "remote_application";
  	var gr = new GlideRecord("sys_app_version");
  	gr.addQuery("source_app_id", update.source_app_id);
  	gr.query();
  	
  	while(gr.next()) {
  		if (app.isValidField("version") && !this.versionComparator.isUpgrade(app.getValue("version"), gr.getValue("version"))) {
  			gs.info("Deleting unneeded app version {0} for app {1}", gr.getValue("version"), app.getValue("sys_id"));
  			this._registerDbUpdates(gr.source_app_id.toString());
  			gr.deleteRecord();
  		}
  	}
  	
  	var notificationUpdateTracker = {
  		isUpdateAvailableChecked : false,
  		isInstallationAvailableChecked : false
  	};

  	//run through and create new records or update existing ones
  	for (var prop in update) {
  		if(gs.nil(update[prop]) || gs.nil(update[prop].version)) continue;

  		//we should only store those which are an upgrade
  		version = update[prop];
  		gs.debug("Saving or updating sys_app_version record for {0} : {1} ({2})", app.getValue("name"), version.version, app.getValue("sys_id"));
  		if (app.isValidField("version") && !this.versionComparator.isUpgrade(app.getValue("version"), version.version))
  			continue;
  		if(this.canSendNotification)
  			this._checkAndUpdatePushNotificationCount(version, app, notificationUpdateTracker);
  		gr.initialize();
  		gr.addQuery("source_app_id", app.getValue("sys_id"));
  		gr.addQuery("version", version.version);
  		gr.query();
  		
  		if (!gr.next())
  			gr.newRecord();
  		
  		gr.setValue(appField, app.getValue("sys_id"));
  		gr.setValue("source_app_id", update.source_app_id);
  		gr.setValue("name", update.app_name);
  		gr.setValue("title", version.title);
  		gr.setValue("number", version.app_number);
  		gr.setValue("scope", update.scope);
  		gr.setValue("version", version.version);
  		gr.setValue("vendor", update.vendor);
  		gr.setValue("vendor_key",update.vendor_key);
  		if(version.short_description)
  			gr.setValue("short_description", version.short_description);
  		gr.setValue("dependencies", version.dependencies);
          gr.setValue("compatibilities", version.compatibilities);
          gr.setValue("is_store_app", version.is_store_app);
          gr.setValue("price_type", version.price_type ? version.price_type : "");
          gr.setValue("lob", (JSON.stringify(version.lob) || '[]'));
          gr.setValue("indicators", (JSON.stringify(version.indicators) || '[]'));
          gr.setValue("display_message", version.display_message ? version.display_message : "");
          gr.setValue("publish_date", version.publish_date);
          gr.setValue("auto_update", update.auto_update);
          gr.setValue("upload_info", version.upload_info ? version.upload_info : "");
          gr.setValue("custom_table_count", version.custom_table_count);
          gr.setValue("needs_app_engine_licensing", version.needs_app_engine_licensing);
  		if(gr.isValidField('delivery_source'))
  			if(version.delivery_source)
  				gr.setValue('delivery_source',version.delivery_source);
  			else
  				gr.setValue('delivery_source','store');
          if (gr.isValidField('block_install'))
            gr.setValue("block_install", version.block_install);

          if (gr.isValidField('block_message'))
            gr.setValue("block_message", version.block_message);

  		if (version.has_demo_data == "true")
  			gr.setValue("demo_data", "has_demo_data");
  		else
  			gr.setValue("demo_data", "no_demo_data");		
  		gr.setValue("uninstall_blocked", version.uninstall_blocked);
  		gr.setValue("hide_on_ui", version.hide_on_ui);
  		gr.setValue("installed_as_dependency", version.installed_as_dependency);
  		gr.setValue("store_latest_updated_time", version.store_latest_updated_time);
  		gr.setValue("repo_latest_updated_time", version.repo_latest_updated_time);
  		gr.setValue("logo", version.logo_sys_id);
  		if (gr.isNewRecord())
  			gr.insert();
  		else
  			gr.update();
  		
  		this._registerDbUpdates(gr.source_app_id.toString());
  	}
  },

  _refreshCustomizationVersions : /*void*/ function(/* GlideRecord sys_app_customization */ customization, /*{}*/ update) {
  	// Update is the customization object which has all the version info
  	var gr = new GlideRecord("sys_app_customization_version");
  	gr.addQuery("app_customization", update.source_customization_id);
  	gr.query();

  	while (gr.next()) {
  		if (customization.isValidField("version") && !this.versionComparator.isUpgrade(customization.getValue("version"), gr.getValue("version"))) {
  			gs.info("Deleting unneeded Customization version {0} for app {1}", gr.getValue("version"), update.app_id);
  			this._registerDbUpdates(gr.app_customization.vendor_app);
  			gr.deleteRecord();
  		}
  	}

  	// Run through and create new records or update existing ones
  	for (var prop in update) {
  		if (gs.nil(update[prop]) || gs.nil(update[prop].customization_version)) continue;

  		// We should only store those which are an upgrade
  		version = update[prop];
  		gs.debug("Saving or updating sys_app_customization_version record for {0} : {1} ({2})", update.app_id, version.customization_version, update.source_customization_id);
  		if (customization.isValidField("version") && !this.versionComparator.isUpgrade(customization.getValue("version"), version.customization_version))
  			continue;
  		gr.initialize();
  		gr.addQuery("app_customization", update.source_customization_id);
  		gr.addQuery("version", version.customization_version);
  		gr.query();

  		if (!gr.next())
  			gr.newRecord();

  		gr.setValue("app_customization", update.source_customization_id);
  		gr.setValue("version", version.customization_version);
  		gr.setValue("vendor_version", version.base_app_version);
  		gr.setValue("vendor_app_id", update.app_id);
  		gr.setValue("scope", version.base_app_scope);
  		gr.setValue("war_version", version.war_version);

  		if (gr.isNewRecord())
  			gr.insert();
  		else
  			gr.update();
  		
  		this._registerDbUpdates(gr.app_customization.vendor_app);
  	}
  },

  _getApp: /*GlideRecord sys_store_app | sys_remote_app */ function(/*string*/ sys_id) {
  	var gr = new GlideRecord("sys_store_app");
      gr.addActiveQuery();
  	gr.addQuery("sys_id", sys_id);
  	gr.query();
  	if (gr.next())
  		return gr;
  	
  	gr = new GlideRecord("sys_remote_app");
  	gr.addQuery("sys_id", sys_id);
  	gr.query();
  	if (gr.next())
  		return gr;
  	
  	return null;
  },
  
  _getCustomization: /*GlideRecord sys_app_customization */ function(/*string*/ sys_id) {
  	var gr = new GlideRecord("sys_app_customization");
  	gr.get(sys_id);
  	if (gr.isValidRecord())
  		return gr;

  	return null;
  },

  _removeWithdrawnAppVersions: function(updatesMap) {
  	var app_version = new GlideRecord("sys_app_version");
  	app_version.query();

      while (app_version.next()) {
          var appId = app_version.getValue("source_app_id");
          var currentVersion = app_version.getValue("version");

          if (gs.nil(updatesMap[appId]) || gs.nil(updatesMap[appId][currentVersion])) {
              gs.debug("Deleting sys_app_version record {0} : {1} ({2}) because it is not in the updates sent from the store.",
                  app_version.getValue("name"), currentVersion, app_version.getValue("number"));
  			this._registerDbUpdates(app_version.source_app_id);
              app_version.deleteRecord();
          }
      }

  },

  _removeWithdrawnCustomizationVersions: function(customizationMap) {
  	var customization = new GlideRecord("sys_app_customization_version");
  	customization.query();

      while (customization.next()) {
          var appId = customization.getValue("vendor_app_id");
          var currentVersion = customization.getValue("version");

          if (gs.nil(customizationMap[appId]) || gs.nil(customizationMap[appId][currentVersion])) {
              gs.debug("Deleting sys_app_customization_version record {0} : {1} ({2}) because it is not in the updates sent from the store.",
                  customization.getValue("scope"), currentVersion, customization.getValue("vendor_app_id"));
  			this._registerDbUpdates(customization.app_customization.vendor_app);
              customization.deleteRecord();
          }
      }
  },

  /**

* Description: queries on sys_offline_app table and data massaging will be done to mimic the store payload
* Returns: updates(offline apps from sys_offline_app table)
*/
  _getOfflineUpdates : function(){
  	var updates = [];
  	var offlineAppsJson = this._buildOfflineApps();
  	var isVersionExists = false;
  	for (var key in offlineAppsJson)
  		updates.push(offlineAppsJson[key]);
  	gs.debug("Got ({0}) candidate updates: {1}", updates.length, this.json.encode(updates));
  	gs.setProperty("sn_appclient.apps_last_sync_time", new GlideDateTime());
  	return {
  		opstatus : 1,
  		data : updates
  	};
  },
  
  _buildOfflineApps : function(){
  	var offlineApps = {};
  	var scopename;
  	var offlineAppsGr = new GlideRecord("sys_offline_app");
  	offlineAppsGr.query();
  	while(offlineAppsGr.next()){
  		scopename = offlineAppsGr.getValue('scope');
  		if(!offlineApps.hasOwnProperty(scopename)){
  			offlineApps[scopename] = {};
  			offlineApps[scopename]['scope'] = offlineAppsGr.getValue('scope');
  			offlineApps[scopename]['app_name'] = offlineAppsGr.getValue('name');
  			offlineApps[scopename]['source_app_id'] = offlineAppsGr.getValue('source_app_id');
  			offlineApps[scopename]['vendor'] = offlineAppsGr.getValue('vendor');
  			offlineApps[scopename]['vendor_key'] = offlineAppsGr.getValue('vendor_prefix');
  			offlineApps[scopename]['auto_update'] = false;
  			offlineApps[scopename]['shared_internally'] = false;
  			offlineApps[scopename]['latest_version'] = offlineAppsGr.getValue('latest_version');
  			offlineApps[scopename]['is_store_app'] =	true;
  			offlineApps[scopename]['indicators'] = new global.JSON().decode(offlineAppsGr.getValue("indicators")) ? new global.JSON().decode(offlineAppsGr.getValue("indicators")) : [];
  			offlineApps[scopename]['display_message'] = offlineAppsGr.getValue('display_message') || "";
  			offlineApps[scopename]['sys_code'] = offlineAppsGr.getValue('sys_code');
  			offlineApps[scopename]['products'] = JSON.parse(offlineAppsGr.getValue('products') || "[]");
  			if(offlineAppsGr.isValidField('uninstall_blocked'))
  				offlineApps[scopename]['uninstall_blocked'] = offlineAppsGr.uninstall_blocked.toString() == 'true'? true: false;
  			if(offlineAppsGr.isValidField('hide_on_ui'))
  				offlineApps[scopename]['hide_on_ui'] = offlineAppsGr.hide_on_ui.toString() == "true" ? true : false;
  			if(offlineAppsGr.isValidField('installed_as_dependency'))
  				offlineApps[scopename]['installed_as_dependency'] = offlineAppsGr.installed_as_dependency.toString() == "true" ? true : false;
  			offlineApps[scopename]['needs_app_engine_licensing'] = offlineAppsGr.needs_app_engine_licensing ? true : false;
  			offlineApps[scopename]['custom_table_count'] = offlineAppsGr.getValue("custom_table_count");
  		}
  		this._addOrCreateVersionForOfflineApp(offlineApps,scopename,offlineAppsGr);
  	}
  	return offlineApps;
  },
  
  _isOfflineAppBlockInstall: function(offlineAppsGr, compatibilities, scope){
  	if (gs.getProperty("sn_appclient.disable_compatibility_check") == "true")
  		return false;
  	return (offlineAppsGr.getValue("delivery_source") == "offline") ? 
  		(compatibilities && compatibilities.toLowerCase().replace(this.spaceRegex,"").split(",").indexOf(this.buildName) == -1) : false;
  },
  
  _addOrCreateVersionForOfflineApp : function(offlineAppsJson,scope,offlineAppsGr){
  	
  	if(!offlineAppsJson[scope].hasOwnProperty('versions')){ 
  		// add version of the new entry to existing version because there is another entry with different version in offline app 
  		offlineAppsJson[scope]['versions'] = [];
  	}
  	
     //stamp latestversion of offlineapp with current identified version if its latest
  	
  	if(offlineAppsJson[scope]['latest_version'] != offlineAppsGr.getValue('latest_version')){
  			if(this.versionComparator.isUpgrade(offlineAppsJson[scope]['latest_version'],offlineAppsGr.getValue('latest_version'))){
  				
  				offlineAppsJson[scope]['latest_version'] = offlineAppsGr.getValue('latest_version');
  				
  			}
  		}
              var offlineCompatibility = {};
              if(this._isOfflineAppBlockInstall(offlineAppsGr, offlineAppsGr.getValue("compatibilities"), scope)){
                      offlineCompatibility.block_install = true;
                      offlineCompatibility.block_message = this.notCertifiedAppBlockMessage;
                      offlineCompatibility.indicators = this.notCertifiedAppIndicator;
              }
  	offlineAppsJson[scope]['versions'].push({
  		'short_description' : offlineAppsGr.getValue("short_description"),
  		'key_features' : offlineAppsGr.getValue("key_features"),
  		'has_demo_data' : (offlineAppsGr.getValue("demo_data") == 'has_demo_data')? "true" : "false",
  		'is_store_app' : true,
  		'title' :  offlineAppsGr.getValue("name"),
  		'release_notes' :offlineAppsGr.getValue("release_notes"),
  		'version': offlineAppsGr.getValue("latest_version"),
  		'compatibilities': offlineAppsGr.getValue("compatibilities"), 
  		'auto_update':false,
  		'dependencies': offlineAppsGr.getValue("dependencies")? offlineAppsGr.getValue("dependencies") : '',
  		'publish_date' : offlineAppsGr.getValue("publish_date"),
  		'price_type' : offlineAppsGr.getValue("price_type"),
  		'indicators': new global.JSON().decode(offlineAppsGr.getValue("indicators")) ? new global.JSON().decode(offlineAppsGr.getValue("indicators")) : (offlineCompatibility.indicators ? offlineCompatibility.indicators : []),
  		'display_message': offlineAppsGr.getValue('display_message') || "",
  		'delivery_source': offlineAppsGr.getValue('delivery_source'),
  		'lob' : new global.JSON().decode(offlineAppsGr.getValue("lob")) ? new global.JSON().decode(offlineAppsGr.getValue("lob")) : [],
  		'uninstall_blocked' : (offlineAppsGr.isValidField('uninstall_blocked') && offlineAppsGr.uninstall_blocked.toString() == 'true') ? true: false,
  		'hide_on_ui' : (offlineAppsGr.isValidField('hide_on_ui') && offlineAppsGr.hide_on_ui.toString() == "true") ? true : false,
  		'installed_as_dependency' : (offlineAppsGr.isValidField('installed_as_dependency') && offlineAppsGr.installed_as_dependency.toString() == "true") ? true : false,
  		'needs_app_engine_licensing': offlineAppsGr.needs_app_engine_licensing ? true : false,
  		'custom_table_count': offlineAppsGr.getValue("custom_table_count"),
  		'lob' : new global.JSON().decode(offlineAppsGr.getValue("lob")) ? new global.JSON().decode(offlineAppsGr.getValue("lob")) : [],
  		'block_install' : offlineCompatibility.block_install ? true : false,
  		'block_message' : offlineCompatibility.block_message ? offlineCompatibility.block_message : "",
  		'logo_hash': this._getLogoHashForOfflineApp(offlineAppsGr.logo)
  	});
  	
  },
  
      _getLogoHashForOfflineApp: function(logoSysId) {
      	if (!logoSysId)
          		return this.constants.NO_LOGO_EXIST;
      	var logoAttachment = new GlideRecord("sys_attachment");
      	logoAttachment.get(logoSysId);
  	return logoAttachment.getValue("hash");
      },
  	
  _deleteAllVersions: function (/*string*/ appId) {
  	gs.debug("Deleting all app versions for source app {0}", appId);
  	var app_versions = new GlideRecord("sys_app_version");
  	app_versions.addQuery("source_app_id", appId);
  	app_versions.query();
  		
  	gs.debug("Number of version records to delete: {0}", app_versions.getRowCount());
  	this._registerDbUpdates(appId);
  	app_versions.deleteMultiple();	
  },
  	
  _deleteHigherVersions: function (/*string*/ appId,/*string*/ highestPossibleVersion) {
  	if (!highestPossibleVersion)
  		return;
  		
  	gs.debug("Deleting app versions for appId {0} higher than {1}", appId, highestPossibleVersion);
  		
  	var app_versions = new GlideRecord("sys_app_version");
  	app_versions.addQuery("source_app_id",appId);
  	app_versions.query();
  		
  	while (app_versions.next()) {
  		if (this.versionComparator.isUpgrade(highestPossibleVersion, app_versions.getValue("version"))) {
  			gs.debug("Deleting sys_app_version record for {0} version {1} because it is higher than the newest version ({2})",
  					 app_versions.getValue("name"), app_versions.getValue("version"), highestPossibleVersion);
  			this._registerDbUpdates(appVersiongr.source_app_id);
  					
  			app_versions.deleteRecord();
  		}
  	}
  },
  
  _versionArrayContains : /*boolean*/ function(/*[{}]*/ versionArray,/*string*/ appId) {
  	for (var i = 0; i< versionArray.length; i++) {
  		var newestVersion = versionArray[i];
  		if (newestVersion.source_app_id == appId)
  			return true;
  	}
  	return false;
  },
  
  _versionArrayContainsVersion: /*boolean*/ function( /*[{}]*/ versionArray,/*string*/ appId,/*string*/ version) {
  	for (var i = 0; i< versionArray.length; i++) {
  		var versionData = versionArray[i];
  		if (versionData.source_app_id == appId && versionData.version == version)
  			return true;
  	}
  	return false;
  },
  
  _setRelevantVersion: function(update, app) {
  	if(!update.hasOwnProperty("latest_version") || gs.nil(update.latest_version))
  		update.latest_version = this._getLatestVersionForUpdate(update);

  	var isAppInstalled = app && app.getRecordClassName() == "sys_store_app";
  	update.relevant_version  = (isAppInstalled && !gs.nil(update[app.getValue("version")])) ? app.getValue("version") : update.latest_version;

  	if(!update[update.relevant_version]) {
  		update[update.relevant_version] = this._getAppVersionDetails(update.source_app_id, update.relevant_version);
  	}
  },
  
  _getLatestVersionForUpdate: function(update) {
  	var versionList = [];
  	for(var prop in update) {
  		if(!gs.nil(update[prop]) && !gs.nil(update[prop].version))
  			versionList.push(update[prop].version);
  	}
  	return new VersionComparator().getLatestVersion(versionList);
  },

  _getLatestVersionForCustomization: function(update) {
  	if (!update.hasOwnProperty("latest_version") || gs.nil(update.latest_version)) {
        var versionList = [];
        for (var prop in update) {
            if (!gs.nil(update[prop]) && !gs.nil(update[prop].customization_version))
                versionList.push(update[prop].customization_version);
        }

        update.latest_version = new VersionComparator().getLatestVersion(versionList);
  	}
  },

  _getHighestVersionNumber: /*{}*/ function(/*string*/ appId, /*[{}]*/ highestVersions) {
  	for (var i = 0; i< highestVersions.length; i++) {
  		var newestVersion = highestVersions[i];
  		if (newestVersion.source_app_id == appId)
  			return newestVersion.version;
  	}
  	gs.debug("No versions found for {1}", appId);
  	return false;
  },

  _areEqual: /*Boolean*/ function(a, b) {
  	if (gs.nil(a) && gs.nil(b))
  		return true;
  	
  	return (a+'') == (b+'');
  },
  
  _areEqualBoolean: /*Boolean*/ function(a, b) {
  	if (gs.nil(a) && gs.nil(b))
  		return true;
  	
  	a = gs.nil(a) ? "false" : a+'';
  	if (a == "true")
  		a = "1";
  	else if (a+'' == "false")
  		a = "0";		

  	b = gs.nil(b) ? "false" : b+'';
  	if (b == "true")
  		b = "1";
  	else if (b == "false")
  		b = "0";
  	
  	return a == b;
  },

  _getAppVersionDetails: function(sourceAppId, version) {
      return this._getInstalledAppVersion(sourceAppId, version) || this._getSysAppVersion(sourceAppId, version) || {};
  },

  _getInstalledAppVersion: function(sourceAppId, version) {
  	var storeAppGr = new GlideRecord("sys_store_app");
  	storeAppGr.addQuery("sys_id", sourceAppId);
  	storeAppGr.addQuery("active", true);
  	storeAppGr.addQuery("version", version);
  	storeAppGr.query();

  	return this._getVersionObject(storeAppGr);
  },

  _getSysAppVersion: function(sourceAppId, version) {
  	var appVersionGr = new GlideRecord("sys_app_version");
  	appVersionGr.addQuery("source_app_id", sourceAppId);
  	appVersionGr.addQuery("version", version);
  	appVersionGr.query();

  	return this._getVersionObject(appVersionGr);
  },

  _getVersionObject: function(/*GlideRecord sys_app_version or sys_store_app*/gr) {
  	var appVersionObj;

  	if (gr.next()) {
  		appVersionObj = {};
  		appVersionObj.auto_update = gr.auto_update + "";
  		appVersionObj.source_app_id = (gr.getTableName() == "sys_app_version" ? gr.source_app_id : gr.sys_id)+"";
  		appVersionObj.version = gr.version + "";
  		appVersionObj.compatibilities = gr.compatibilities + "";
  		appVersionObj.custom_table_count = (gr.custom_table_count || 0) + "";
  		appVersionObj.dependencies = gr.dependencies + "";
  		appVersionObj.has_demo_data = (gr.demo_data == "has_demo_data") + "";
  		appVersionObj.hide_on_ui = gr.hide_on_ui.toString() == "true";
  		appVersionObj.indicators = (gs.nil(gr.indicators) || gr.indicators == "undefined") ? [] : JSON.parse(gr.getValue("indicators"));
  		appVersionObj.installed_as_dependency = gr.installed_as_dependency.toString() == "true";
  		appVersionObj.is_store_app = gr.is_store_app.toString() == "true";
  		appVersionObj.lob = (gs.nil(gr.lob) || gr.lob == "undefined") ? [] : JSON.parse(gr.getValue("lob"));
  		appVersionObj.needs_app_engine_licensing = gr.needs_app_engine_licensing.toString() == "true";
  		appVersionObj.price_type = gr.price_type + "";
  		appVersionObj.short_description = gr.short_description + "";
  		appVersionObj.store_latest_updated_time = gr.store_latest_updated_time + "";
  		appVersionObj.repo_latest_updated_time = gr.repo_latest_updated_time + "";
  		appVersionObj.title = (gr.getTableName() == "sys_app_version" ? gr.title : gr.name) + "";
  		appVersionObj.uninstall_blocked = gr.uninstall_blocked.toString() == "true";
  		if(gr.isValidField("number"))
  			appVersionObj.app_number = gr.number + "";
  		if(gr.isValidField("block_install"))
  			appVersionObj.block_install = gr.block_install.toString() == "true";
  		if(gr.isValidField("block_message"))
  			appVersionObj.block_message = gr.block_message + "";
  		if(gr.isValidField("publish_date"))
  			appVersionObj.publish_date = gr.publish_date + "";
  		if(gr.isValidField("display_message"))
  			appVersionObj.display_message = gr.display_message + "";
  		if(gr.isValidField("upload_info"))
  			appVersionObj.upload_info = gr.upload_info + "";
  		if(gr.isValidField("shared_internally"))
  			appVersionObj.shared_internally = gr.shared_internally.toString() == "true";
  	}

  	gs.debug("appVersionObj: " + JSON.stringify(appVersionObj));
  	return appVersionObj;
  },

  _getAllAppVersionSelectedProperties: function() {
  	var properties = ["logo", "block_install", "block_message", "auto_update", "shared_internally", "store_latest_updated_time",
                       "repo_latest_updated_time", "indicators", "dependencies", "needs_app_engine_licensing", "store_application", "latest_version"];

  	var appVersionMap = {};

  	var gr = new GlideRecord("sys_app_version");
  	gr.query();

  	this._getPropertiesFromGlideRecord(gr, properties, appVersionMap);

  	gr = new GlideRecord("sys_store_app");
  	gr.addNotNullQuery("last_installed_from");
  	gr.query();

  	this._getPropertiesFromGlideRecord(gr, properties, appVersionMap);

  	gs.info("appVersionMap:" + JSON.stringify(appVersionMap));
  	return appVersionMap;
  },

  _getPropertiesFromGlideRecord: function(gr, properties, appVersionMap) {
  	while (gr.next()) {
  		var sourceAppId = gr.getTableName() == "sys_app_version" ? gr.source_app_id : gr.sys_id;
  		if (!appVersionMap[sourceAppId]) appVersionMap[sourceAppId] = {};
  		if (!appVersionMap[sourceAppId][gr.version]) appVersionMap[sourceAppId][gr.version] = {};

  		if(gr.getTableName() == "sys_store_app") {
  			if(!gr.active || !gr.version) {
  				delete appVersionMap[sourceAppId];
  				continue;
  			}
  			appVersionMap[sourceAppId].installed = true;
  		}

  		for (i in properties) {
  			if (gr.isValidField(properties[i])) {
  				var value = gr[properties[i]].toString();
  				if (properties[i] == "indicators")
  					value = (gs.nil(value) || value == "undefined") ? [] : JSON.parse(value);
  				appVersionMap[sourceAppId][gr.version][properties[i]] = value;
  			}
  		}
  	}
  },

  _processDeltaPayload: function(storePayload, args) {
  	var allAppVersionSelectedProperties = this._getAllAppVersionSelectedProperties();

  	// adding some app level properties to version, as filtering is done at version level in next step
  	this._addAppLevelPropertiesToVersionForFiltering(storePayload);

  	var customizationMap = this._getDeltaCustomizationMap(storePayload);

  	// remove app versions present on instance
  	this._filterAppVersionsPresentOnInstance(allAppVersionSelectedProperties, storePayload);

  	// adding delete action for app versions not sent from store
  	this._processAppVersionsNotPresentOnStore(allAppVersionSelectedProperties, storePayload);

  	// remove app objects with no version
  	this._removeAppsWithNoVersion(storePayload);

  	gs.info("Processing ({0}) apps: {1}", storePayload.length, JSON.stringify(storePayload));

  	var updates = this._getUpdates(storePayload);
  	if (!updates) return;

  	//remove apps where a local version exists
  	this._filterLocalApps(updates.appMap);

  	//update app records in sys_store_app, sys_remote_app and sys_app_version tables
  	this._updateAppRecords(updates.appMap);
  	// Update app records in sys_app_customization and sys_app_customization_version tables
  	this._updateCustomizations(customizationMap);

  	// update logos async
  	this._downloadLogosAsync();

  	this.refreshStoreResponseCache(args);
  },

  // adding some app level properties to version, as filtering is done at version level in next step
  _addAppLevelPropertiesToVersionForFiltering: function(apps) {
  	var appProps = ["auto_update", "shared_internally", "latest_version"];
  	apps.forEach(function(app) {
  		if (app.versions) {
  			app.versions.forEach(function(versionObj) {
  				appProps.forEach(function(key) {
  					versionObj[key] = app[key];
  				});
  				versionObj.logo = versionObj.logo_sys_id;
  			});
  		}
  	});
  },

  _filterAppVersionsPresentOnInstance: function(allAppVersionSelectedProperties, apps) {
      apps.forEach(function(app) {
          if (app.versions) {
              app.versions = app.versions.filter(function(versionObj) {
                  if (!allAppVersionSelectedProperties[app.source_app_id] ||
                      !allAppVersionSelectedProperties[app.source_app_id][versionObj.version] ||
                      (allAppVersionSelectedProperties[app.source_app_id]["installed"] &&
                          allAppVersionSelectedProperties[app.source_app_id][versionObj.version].hasOwnProperty("store_application") &&
                          !allAppVersionSelectedProperties[app.source_app_id][versionObj.version]["store_application"])) {
                      return true;
                  }

                  var appVersionObjSelectedProperties = allAppVersionSelectedProperties[app.source_app_id][versionObj.version];
                  appVersionObjSelectedProperties.found = true;

                  for (var key in appVersionObjSelectedProperties) {
                      if (key == "found" || gs.nil(versionObj[key]))
                          continue;

                      var versionObjValue = key == "indicators" ? JSON.stringify(versionObj[key]) : versionObj[key].toString();
                      var selectedPropertyValue = key == "indicators" ? JSON.stringify(appVersionObjSelectedProperties[key]) : appVersionObjSelectedProperties[key].toString();
                      if (versionObjValue != selectedPropertyValue) {
                          gs.debug("Retaining app {0} version {1} as {2} has changed", app.app_name, versionObj.version, key);
                          return true;
                      }
                  }
                  return false;
              });
          }
      });
  },

  _processAppVersionsNotPresentOnStore: function(allAppVersionSelectedProperties, apps) {
  	if(this.specificAppsToUpdate !== "[]")
  		return;
      for (var appId in allAppVersionSelectedProperties) {
          for (var key in allAppVersionSelectedProperties[appId]) {
              if (key != "installed" && !allAppVersionSelectedProperties[appId][key].found) {

                  var appVersiongr = new GlideRecord("sys_app_version");
                  appVersiongr.addQuery("source_app_id", appId);
                  appVersiongr.addQuery("version", key);
                  appVersiongr.query();
                  if (appVersiongr.next()) {
  					gs.info("Trying to delete: " + appId + " version: " + key);
  					this._registerDbUpdates(appId);
                      appVersiongr.deleteRecord();
                  }

                  var remoteAppGr = new GlideRecord("sys_remote_app");
                  remoteAppGr.addQuery("source_app_id", appId);
                  remoteAppGr.addQuery("latest_version", key);
                  remoteAppGr.query();
                  if (remoteAppGr.next()) {
  					gs.info("Trying to delete: " + appId + " version: " + key);
  					this._registerDbUpdates(remoteAppGr.getUniqueValue());
                      remoteAppGr.deleteRecord();
                  }
              }
          }
      }
  },

  _removeAppsWithNoVersion: function(apps) {
      for (var i = 0; i < apps.length; i++) {
          if (apps[i] && !apps[i].versions) {
              apps.splice(i, 1);
              i--;
          }
      }
  },
  
  _getPluginActivationEnforcementMode: function() {
  	if (typeof sn_lef.GlideEntitlement.getPluginActivationMode === "function")
  		return sn_lef.GlideEntitlement.getPluginActivationMode();
  	else
  		return '';
  },
  
  _getIsInstanceLicenseAware: function() {
  	if (typeof sn_lef.GlideEntitlement.getLicenseAwareMode === "function")
  		return sn_lef.GlideEntitlement.getLicenseAwareMode();
  	else
  		return '';
  },
  
  _registerDbUpdates: function(sysId) {
  	if(!gs.nil(sysId))
  		this.appsUpdatedInDB[sysId] = true;
  },

  type: 'UpdateChecker'
};

Sys ID

46e8ba01eb002100d4360c505206fec3

Offical Documentation

Official Docs: