From 0a9cd5b037d03d366442cff3361230a3a1d8420f Mon Sep 17 00:00:00 2001 From: Emmanuel Yupit Date: Tue, 10 Jun 2025 09:27:14 -0500 Subject: [PATCH] Improve token handling to prevent login prompts in V2 UI - Added token caching with 1-hour expiry - Implemented retry logic with exponential backoff - Added request deduplication for token requests - Improved error handling for failed token requests - Prevented auth redirects for token requests - Added graceful degradation for non-critical operations --- .../ClientResources/Scripts/siteimprove.js | 138 +++++++++++++++--- 1 file changed, 118 insertions(+), 20 deletions(-) diff --git a/SiteImprove.Optimizely.Plugin/modules/_protected/SiteImprove.Optimizely.Plugin_files/1.0.5/ClientResources/Scripts/siteimprove.js b/SiteImprove.Optimizely.Plugin/modules/_protected/SiteImprove.Optimizely.Plugin_files/1.0.5/ClientResources/Scripts/siteimprove.js index bb35e79..4c2dbb6 100644 --- a/SiteImprove.Optimizely.Plugin/modules/_protected/SiteImprove.Optimizely.Plugin_files/1.0.5/ClientResources/Scripts/siteimprove.js +++ b/SiteImprove.Optimizely.Plugin/modules/_protected/SiteImprove.Optimizely.Plugin_files/1.0.5/ClientResources/Scripts/siteimprove.js @@ -1,4 +1,4 @@ -define([ +define([ "dojo", "dojo/_base/declare", "epi/_Module", @@ -7,7 +7,8 @@ "dojo/request", "epi/shell/_ContextMixin", "dojo/when", - "siteimprove/SiteimproveCommandProvider" + "siteimprove/SiteimproveCommandProvider", + "dojo/_base/lang" ], function ( dojo, declare, @@ -17,11 +18,17 @@ request, _ContextMixin, when, - SiteimproveCommandProvider + SiteimproveCommandProvider, + lang ) { return declare([_ContextMixin], { isPublishing: false, isInitialized: false, + _token: null, + _tokenExpiry: null, + _tokenRetryCount: 0, + _maxTokenRetries: 3, + _tokenRequestInProgress: false, constructor: function () { var scope = this; when(scope.getCurrentContext(), @@ -127,31 +134,122 @@ /** * Request token from backoffice and sends request to SiteImprove */ - pushSi: function (method, url, callback) { + /** + * Request token from backoffice and sends request to SiteImprove + * @param {string} method - The method to call on the SiteImprove object + * @param {string} url - The URL to pass to the method + * @param {Function} callback - Callback function to execute after the method call + * @param {boolean} isRetry - Internal flag to indicate if this is a retry attempt + */ + pushSi: function (method, url, callback, isRetry) { var si = window._si || []; + var scope = this; - if (method === 'clear') { //special case, does not ask for token + // Special case - doesn't require a token + if (method === 'clear') { + si.push([ + method, + function () { + if (callback) { + callback(); + } + } + ]); + return; + } + + // Check if we have a valid cached token + if (this._token && this._tokenExpiry && this._tokenExpiry > new Date().getTime()) { + this._executeSiCall(si, method, url, callback, this._token); + return; + } + + // Prevent multiple concurrent token requests + if (this._tokenRequestInProgress) { + setTimeout(function() { + scope.pushSi(method, url, callback); + }, 500); + return; + } + + // Reset retry counter if not a retry + if (!isRetry) { + this._tokenRetryCount = 0; + } + + // If we've exceeded max retries, give up silently for non-critical operations + if (this._tokenRetryCount >= this._maxTokenRetries) { + console.warn('Max token retries reached. Operation skipped.'); + if (callback) callback(); + return; + } + + // Request a new token + this._tokenRequestInProgress = true; + request.get(window.epi.routes.getActionPath({ + moduleArea: "SiteImprove.Optimizely.Plugin", + controller: "Siteimprove", + action: "token" + }), { + handleAs: 'json', + headers: { + 'X-Requested-With': null // Prevent auth redirects + } + }) + .then(function (response) { + scope._tokenRequestInProgress = false; + scope._tokenRetryCount = 0; + + // Cache the token with a 1-hour expiry + scope._token = response; + scope._tokenExpiry = new Date().getTime() + (60 * 60 * 1000); + + // Execute the original call with the new token + scope._executeSiCall(si, method, url, callback, response); + }) + .otherwise(function(error) { + scope._tokenRequestInProgress = false; + scope._token = null; + scope._tokenExpiry = null; + + // Only show login prompt for critical operations + if (method === 'recheck' || method === 'input') { + scope._tokenRetryCount++; + if (scope._tokenRetryCount < scope._maxTokenRetries) { + // Retry with exponential backoff + setTimeout(function() { + scope.pushSi(method, url, callback, true); + }, 1000 * Math.pow(2, scope._tokenRetryCount)); + } else { + console.error('Failed to get token after multiple attempts:', error); + if (callback) callback(); + } + } else { + // For non-critical operations, just skip if we can't get a token + if (callback) callback(); + } + }); + }, + + /** + * Execute the SiteImprove call with the provided token + * @private + */ + _executeSiCall: function(si, method, url, callback, token) { + try { si.push([ - method, function () { - //console.log('SiteImprove pass: ' + method + (callback ? " with callback" : "")); + method, + url, + token, + function() { if (callback) { callback(); } } ]); - } else { - request.get(window.epi.routes.getActionPath({ moduleArea: "SiteImprove.Optimizely.Plugin", controller: "Siteimprove", action: "token" }), { handleAs: 'json' }) - .then(function (response) { - // relay to SiteImprove - si.push([ - method, url, response, function () { - //console.log('SiteImprove pass: ' + method + ' - ' + url + (callback ? " with callback" : "")); - if (callback) { - callback(); - } - } - ]); - }.bind(this)); + } catch (e) { + console.error('Error executing SiteImprove call:', e); + if (callback) callback(); } },