diff --git a/test/utils/experiment-provider.test.js b/test/utils/experiment-provider.test.js index 3a397cacd..25f1378f2 100644 --- a/test/utils/experiment-provider.test.js +++ b/test/utils/experiment-provider.test.js @@ -1,6 +1,6 @@ /* eslint-disable no-underscore-dangle */ import { expect } from '@esm-bundle/chai'; -import { getExperimentData, getDecisionScopesForVerb } from '../../unitylibs/utils/experiment-provider.js'; +import getExperimentData, { getDecisionScopesForVerb } from '../../unitylibs/utils/experiment-provider.js'; describe('getExperimentData', () => { // Helper function to setup mock with result and error diff --git a/unitylibs/core/widgets/prompt-bar-upload/prompt-bar-upload.css b/unitylibs/core/widgets/prompt-bar-upload/prompt-bar-upload.css index c48db813a..59a6db0a1 100644 --- a/unitylibs/core/widgets/prompt-bar-upload/prompt-bar-upload.css +++ b/unitylibs/core/widgets/prompt-bar-upload/prompt-bar-upload.css @@ -943,8 +943,7 @@ margin: 0; .pbu-preview { position: absolute; -top: 0; -left: 0; +inset: 0; width: 100%; height: 100%; box-sizing: border-box; @@ -958,11 +957,14 @@ display: none; .pbu-preview-img { display: block; -width: stretch; -height: stretch; +width: 100%; +height: 100%; +max-width: none; object-fit: cover; +object-position: center; border-radius: 13.667px; border: 2px solid #4069FD; +box-sizing: border-box; } .unity-prompt-bar-upload.unity-enabled .pbu-delete-btn { diff --git a/unitylibs/core/workflow/workflow-acrobat/action-binder.js b/unitylibs/core/workflow/workflow-acrobat/action-binder.js index e679a60a4..2c50b7db8 100644 --- a/unitylibs/core/workflow/workflow-acrobat/action-binder.js +++ b/unitylibs/core/workflow/workflow-acrobat/action-binder.js @@ -162,7 +162,7 @@ export default class ActionBinder { this.actionMap = actionMap; this.limits = {}; this.operations = []; - this.acrobatApiConfig = this.getAcrobatApiConfig(); + this.acrobatApiConfig = null; this.networkUtils = new NetworkUtils(); this.uploadHandler = null; this.splashScreenEl = null; @@ -183,6 +183,10 @@ export default class ActionBinder { this.multiFileValidationFailure = false; this.initialize(); this.experimentData = null; + this.experimentViaPageConfig = false; + this.pageConfigLocation = null; + this.pageConfigFetched = false; + this.pageConfigPromise = null; } async initialize() { @@ -225,11 +229,13 @@ export default class ActionBinder { } getAcrobatApiConfig() { + const base = this.pageConfigLocation ? `${this.pageConfigLocation}/api/v1` : unityConfig.apiEndPoint; unityConfig.acrobatEndpoint = { - createAsset: `${unityConfig.apiEndPoint}/asset`, - finalizeAsset: `${unityConfig.apiEndPoint}/asset/finalize`, - getMetadata: `${unityConfig.apiEndPoint}/asset/metadata`, + createAsset: `${base}/asset`, + finalizeAsset: `${base}/asset/finalize`, + getMetadata: `${base}/asset/metadata`, }; + unityConfig.connectorApiEndPoint = `${base}/asset/connector`; return unityConfig; } @@ -243,18 +249,6 @@ export default class ActionBinder { } async handlePreloads() { - if (!this.experimentData && this.workflowCfg.targetCfg?.experimentationOn?.includes(this.workflowCfg.enabledFeatures[0])) { - const { getExperimentData, getDecisionScopesForVerb } = await import('../../../utils/experiment-provider.js'); - try { - const decisionScopes = await getDecisionScopesForVerb(this.workflowCfg.enabledFeatures[0]); - this.experimentData = await getExperimentData(decisionScopes); - } catch (error) { - await this.dispatchErrorToast('warn_fetch_experiment', null, error.message, true, true, { - code: 'warn_fetch_experiment', - desc: error.message, - }); - } - } const parr = []; if (this.workflowCfg.targetCfg.showSplashScreen) { parr.push( @@ -264,6 +258,32 @@ export default class ActionBinder { await priorityLoad(parr); } + async ensurePageConfig() { + if (this.pageConfigFetched) return; + this.pageConfigFetched = true; + const verb = this.workflowCfg.enabledFeatures[0]; + try { + const { fetchPageConfig } = await import('../../../scripts/utils.js'); + const { default: getExperimentData } = await import('../../../utils/experiment-provider.js'); + const pageConfig = await fetchPageConfig({ product: 'acrobat', verb }); + this.pageConfigLocation = pageConfig.location; + if (pageConfig.config?.target?.enabled) { + this.experimentData = await getExperimentData(pageConfig.config.target.decisionScopes); + this.experimentViaPageConfig = true; + } else if (!this.experimentData && this.workflowCfg.targetCfg?.experimentationOn?.includes(verb)) { + const { getDecisionScopesForVerb } = await import('../../../utils/experiment-provider.js'); + const decisionScopes = await getDecisionScopesForVerb(verb); + this.experimentData = await getExperimentData(decisionScopes); + } + } catch (error) { + await this.dispatchErrorToast('warn_fetch_experiment', null, error.message, true, true, { + code: 'warn_fetch_experiment', + desc: error.message, + }); + } + this.acrobatApiConfig = this.getAcrobatApiConfig(); + } + async dispatchErrorToast(errorType, status, info = null, lanaOnly = false, showError = true, errorMetaData = {}) { if (!showError) return; const errorMessage = errorType in this.workflowCfg.errors @@ -458,7 +478,7 @@ export default class ActionBinder { redirectUrl = url.href; } } - this.redirectUrl = redirectUrl; + this.redirectUrl = redirectUrl; }) .catch(async (e) => { await this.showTransitionScreen(); @@ -487,7 +507,7 @@ export default class ActionBinder { if (this.multiFileValidationFailure) cOpts.payload.feedback = 'uploaderror'; if (this.showInfoToast) cOpts.payload.feedback = 'nonpdf'; } - if (this.workflowCfg.targetCfg?.experimentationOn?.includes(this.workflowCfg.enabledFeatures[0]) && this.experimentData) { + if (this.experimentData && (this.experimentViaPageConfig || this.workflowCfg.targetCfg?.experimentationOn?.includes(this.workflowCfg.enabledFeatures[0]))) { cOpts.payload.variationId = this.experimentData.variationId; } await this.getRedirectUrl(cOpts); @@ -557,6 +577,7 @@ export default class ActionBinder { if (prevalidatedFiles.length === 0) return; const { isValid, validFiles } = await this.validateFiles(prevalidatedFiles); if (!isValid) return; + await (this.pageConfigPromise || this.ensurePageConfig()); await this.initUploadHandler(); if (files.length === 1 || (validFiles.length === 1 && !verbsWithoutFallback.includes(this.workflowCfg.enabledFeatures[0]))) { await this.handleSingleFileUpload(validFiles); @@ -768,6 +789,7 @@ export default class ActionBinder { } if (b === this.block) { this.loadTransitionScreen(); + this.pageConfigPromise = this.ensurePageConfig(); } } } diff --git a/unitylibs/core/workflow/workflow-acrobat/target-config.json b/unitylibs/core/workflow/workflow-acrobat/target-config.json index ab7f56ad4..99b2f2da5 100644 --- a/unitylibs/core/workflow/workflow-acrobat/target-config.json +++ b/unitylibs/core/workflow/workflow-acrobat/target-config.json @@ -22,6 +22,7 @@ "fetchApiConfig": { "finalizeAsset": { "retryType": "polling", + "retryOn202": false, "retryParams": { "maxRetryDelay": 300000, "defaultRetryDelay": 5000 diff --git a/unitylibs/core/workflow/workflow-acrobat/upload-handler.js b/unitylibs/core/workflow/workflow-acrobat/upload-handler.js index ba04bef4e..de9a008e6 100644 --- a/unitylibs/core/workflow/workflow-acrobat/upload-handler.js +++ b/unitylibs/core/workflow/workflow-acrobat/upload-handler.js @@ -215,6 +215,7 @@ export default class UploadHandler { this.actionBinder.acrobatApiConfig.acrobatEndpoint.finalizeAsset, finalizeOpts, this.actionBinder.workflowCfg.targetCfg.fetchApiConfig.finalizeAsset, + (responseJson) => responseJson, ); if (!finalizeJson || Object.keys(finalizeJson).length !== 0) { if (this.actionBinder.MULTI_FILE) { diff --git a/unitylibs/scripts/utils.js b/unitylibs/scripts/utils.js index 6e023f25c..8e573d33c 100644 --- a/unitylibs/scripts/utils.js +++ b/unitylibs/scripts/utils.js @@ -307,12 +307,14 @@ export const unityConfig = (() => { prod: { apiEndPoint: 'https://unity.adobe.io/api/v1', connectorApiEndPoint: 'https://unity.adobe.io/api/v1/asset/connector', + pageConfigEndPoint: 'https://cdn-unity.adobe.com/api/v1/pageConfig', env: 'prod', ...commoncfg, }, stage: { apiEndPoint: 'https://unity-stage.adobe.io/api/v1', connectorApiEndPoint: 'https://unity-stage.adobe.io/api/v1/asset/connector', + pageConfigEndPoint: 'https://cdn-unity.stage.adobe.com/api/v1/pageConfig', env: 'stage', ...commoncfg, }, @@ -345,3 +347,14 @@ export function sendAnalyticsEvent(event) { export function getMatchedDomain(domainMap = {}, hostname = window.location.hostname) { return Object.keys(domainMap).find((domain) => domainMap[domain].some((pattern) => new RegExp(pattern).test(hostname))); } + +export async function fetchPageConfig({ product, verb }) { + try { + const url = `${unityConfig.pageConfigEndPoint}?product=${product}&verb=${verb}`; + const resp = await fetch(url, { headers: { 'x-api-key': unityConfig.apiKey } }); + if (!resp.ok) throw new Error(`PageConfig fetch failed: ${resp.statusText}`); + return resp.json(); + } catch (e) { + return {}; + } +} diff --git a/unitylibs/utils/NetworkUtils.js b/unitylibs/utils/NetworkUtils.js index 555628b9e..20f7dff1b 100644 --- a/unitylibs/utils/NetworkUtils.js +++ b/unitylibs/utils/NetworkUtils.js @@ -122,6 +122,14 @@ export default class NetworkUtils { } } + shouldRetryPollingRequest(status, retryConfig, customRetryCheckResult) { + if (customRetryCheckResult) return true; + if (status >= 500 && status < 600) return true; + if (status === 429) return true; + if (status === 202 && retryConfig.retryOn202 !== false) return true; + return false; + } + async fetchFromServiceWithServerPollingRetry(url, options, retryConfig, onSuccess, onError) { const maxRetryDelay = retryConfig.retryParams?.maxRetryDelay || 300000; let timeLapsed = 0; @@ -133,7 +141,7 @@ export default class NetworkUtils { const { status, headers } = response; const responseJson = await this.getResponseJson(response); const customRetryCheckResult = retryConfig.extraRetryCheck && await retryConfig.extraRetryCheck(status, responseJson); - if (customRetryCheckResult || status === 202 || (status >= 500 && status < 600) || status === 429) { + if (this.shouldRetryPollingRequest(status, retryConfig, customRetryCheckResult)) { const retryDelay = (parseInt(headers.get('retry-after'), 10) * 1000) || retryConfig.retryParams?.defaultRetryDelay || 5000; await new Promise((resolve) => { setTimeout(resolve, retryDelay); }); timeLapsed += retryDelay; diff --git a/unitylibs/utils/experiment-provider.js b/unitylibs/utils/experiment-provider.js index 013755c2c..f102ba724 100644 --- a/unitylibs/utils/experiment-provider.js +++ b/unitylibs/utils/experiment-provider.js @@ -1,11 +1,5 @@ /* eslint-disable no-underscore-dangle */ -export async function getDecisionScopesForVerb(verb) { - const region = await getRegion().catch(() => undefined); - const verbScope = `acom_unity_acrobat_${verb}`; - return region ? [`${verbScope}_${region}`, verbScope] : [verbScope]; -} - export async function getRegion() { const resp = await fetch('https://geo2.adobe.com/json/', { cache: 'no-cache' }); if (!resp.ok) throw new Error(`Failed to resolve region: ${resp.statusText}`); @@ -14,7 +8,13 @@ export async function getRegion() { return country.toLowerCase(); } -export async function getExperimentData(decisionScopes) { +export async function getDecisionScopesForVerb(verb) { + const region = await getRegion().catch(() => undefined); + const verbScope = `acom_unity_acrobat_${verb}`; + return region ? [`${verbScope}_${region}`, verbScope] : [verbScope]; +} + +export default async function getExperimentData(decisionScopes) { if (!decisionScopes || decisionScopes.length === 0) { throw new Error('No decision scopes provided for experiment data fetch'); }