1/** 2 * CA-05 â Comprehensive Analysis website UX. 3 * Consumes existing Comprehensive start/status APIs. No engine changes. 4 */ 5(function(global) { 6 'use strict'; 7 8 var _inFlightByOriginal = Object.create(null); 9 var _pollById = Object.create(null); 10 var _cardByOriginal = Object.create(null); 11 var _LS_KEY = 'sm_ca_run_v1'; 12 13 function _isAr() { 14 if (typeof global._ui === 'function') { 15 try { return document.documentElement.lang === 'ar'; } catch (_e) {} 16 } 17 return (global.PresentationContext && global.PresentationContext.language === 'ar') 18 || (document.documentElement && document.documentElement.lang === 'ar'); 19 } 20 21 function _t(en, ar) { 22 return _isAr() ? ar : en; 23 } 24 25 var _runtime = null; 26 27 function _phaseMessage(phase, status, freshness) { 28 if (status === 'FAILED') { 29 return _t('Could not complete comprehensive analysis. Please try again.', 30 'تعذر Ø¥ÙÙ Ø§Ù Ø§ÙØªØÙÙÙ Ø§ÙØ´Ø§Ù Ù. ØØ§Ù٠٠رة أخرÙ.'); 31 } 32 if (freshness === 'STALE' && status === 'READY' && phase === 'PROCESSED_READY') { 33 return _t( 34 'This result needs an update before the latest analysis can be shown.', 35 'ÙØ°Ù اÙÙØªÙجة ØªØØªØ§Ø¬ Ø¥ÙÙ ØªØØ¯ÙØ« ÙØ¨Ù عرض Ø£ØØ¯Ø« تØÙÙÙ.', 36 ); 37 } 38 if (status === 'READY' && phase === 'PROCESSED_READY') { 39 return _t('Comprehensive analysis complete', 'Ø§ÙØªÙ Ù Ø§ÙØªØÙÙÙ Ø§ÙØ´Ø§Ù Ù'); 40 } 41 if (status === 'READY' && phase !== 'PROCESSED_READY') { 42 return _t('Almost doneâ¦', 'Ø§ÙØªØ±Ø¨Ùا Ù Ù Ø§ÙØ§ÙØªÙØ§Ø¡...'); 43 } 44 var map = { 45 INITIALIZING: _t('Searching for the best resultsâ¦', 'Ø¬Ø§Ø±Ù Ø§ÙØ¨ØØ« Ø¹Ù Ø£ÙØ¶Ù اÙÙØªØ§Ø¦Ø¬...'), 46 EXPANDED_RUNNING: _t('Expanding the searchâ¦', 'Ø¬Ø§Ø±Ù ØªÙØ³Ùع ÙØ·Ø§Ù Ø§ÙØ¨ØØ«...'), 47 EXPANDED_READY: _t('Collecting more productsâ¦', 'جار٠ج٠ع اÙÙ Ø²ÙØ¯ ٠٠اÙÙ ÙØªØ¬Ø§Øª...'), 48 SECOND_LEVEL_RUNNING: _t('Analyzing the resultsâ¦', 'جار٠تØÙÙ٠اÙÙØªØ§Ø¦Ø¬...'), 49 SECOND_LEVEL_READY: _t('Ranking the best optionsâ¦', 'Ø¬Ø§Ø±Ù ØªØ±ØªÙØ¨ Ø£ÙØ¶Ù Ø§ÙØ®Ùارات...'), 50 AGGREGATING: _t('Ranking the best optionsâ¦', 'Ø¬Ø§Ø±Ù ØªØ±ØªÙØ¨ Ø£ÙØ¶Ù Ø§ÙØ®Ùارات...'), 51 PROCESSING: _t('Preparing the best results for youâ¦', 'جار٠تجÙÙØ² Ø£ÙØ¶Ù اÙÙØªØ§Ø¦Ø¬ ÙÙ...'), 52 }; 53 return map[phase] || _t('Searching for the best resultsâ¦', 'Ø¬Ø§Ø±Ù Ø§ÙØ¨ØØ« Ø¹Ù Ø£ÙØ¶Ù اÙÙØªØ§Ø¦Ø¬...'); 54 } 55 56 function _progressIcon(phase, status, freshness) { 57 if (status === 'FAILED') return 'â ï¸'; 58 if (freshness === 'STALE' && phase === 'PROCESSED_READY') return 'â¹ï¸'; 59 if (status === 'READY' && phase === 'PROCESSED_READY') return 'â'; 60 if (phase === 'PROCESSING') return 'â¨'; 61 if (phase === 'AGGREGATING' || phase === 'SECOND_LEVEL_READY') return 'â¨'; 62 if (phase === 'SECOND_LEVEL_RUNNING') return 'ð'; 63 return 'ð'; 64 } 65 66 function _progressPercent(data, previousPct) { 67 data = data || {}; 68 var status = data.status || 'RUNNING'; 69 var phase = data.phase || 'INITIALIZING'; 70 var freshness = data.freshnessStatus || ''; 71 var prev = Math.max(0, Math.min(100, Math.round(previousPct || 0))); 72 if (status === 'FAILED') { 73 return Math.min(prev || 0, 99); 74 } 75 if (status === 'READY' && phase === 'PROCESSED_READY' && freshness === 'STALE') { 76 return Math.min(Math.max(prev, 90), 90); 77 } 78 if (status === 'READY' && phase === 'PROCESSED_READY') { 79 return 100; 80 } 81 var next = 5; 82 if (status === 'READY' && phase !== 'PROCESSED_READY') next = 95; 83 else if (phase === 'INITIALIZING' || phase === 'PENDING') next = 5; 84 else if (phase === 'EXPANDED_RUNNING') next = 20; 85 else if (phase === 'EXPANDED_READY') next = 30; 86 else if (phase === 'SECOND_LEVEL_RUNNING') { 87 var sl = data.secondLevel || {}; 88 var total = Number(sl.seedsDiscovered) || 0; 89 var done = (Number(sl.completed) || 0) + (Number(sl.failed) || 0); 90 if (total > 0) next = Math.round(35 + (40 * (done / total))); 91 else next = 35; 92 } else if (phase === 'SECOND_LEVEL_READY' || phase === 'AGGREGATING') next = 80; 93 else if (phase === 'PROCESSING') next = 90; 94 if (next > 100) next = 100; 95 if (next < 0) next = 0; 96 return Math.max(prev, next); 97 } 98 99 function _stageIndex(phase) {
100 if (phase === 'PROCESSING' || phase === 'PROCESSED_READY') return 4; 101 if (phase === 'AGGREGATING') return 3; 102 if (phase === 'SECOND_LEVEL_RUNNING' || phase === 'SECOND_LEVEL_READY') return 2; 103 if (phase === 'EXPANDED_RUNNING' || phase === 'EXPANDED_READY') return 1; 104 return 0; 105 } 106 107 function _loadStore() { 108 try { 109 return JSON.parse(localStorage.getItem(_LS_KEY) || '{}') || {}; 110 } catch (_e) { 111 return {}; 112 } 113 } 114 115 function _saveRun(originalAnalysisId, comprehensiveAnalysisId, productId) { 116 if (!originalAnalysisId || !comprehensiveAnalysisId) return; 117 var store = _loadStore(); 118 store[String(originalAnalysisId)] = { 119 ca: String(comprehensiveAnalysisId), 120 pid: productId ? String(productId) : '', 121 }; 122 try { localStorage.setItem(_LS_KEY, JSON.stringify(store)); } catch (_e) {} 123 } 124 125 function _savedEntry(originalAnalysisId) { 126 var raw = _loadStore()[String(originalAnalysisId)]; 127 if (!raw) return null; 128 if (typeof raw === 'string') return { ca: raw, pid: '' }; 129 return { ca: String(raw.ca || ''), pid: String(raw.pid || '') }; 130 } 131 132 function _savedRun(originalAnalysisId) { 133 var entry = _savedEntry(originalAnalysisId); 134 return entry && entry.ca ? entry.ca : ''; 135 } 136 137 function _clearRun(originalAnalysisId) { 138 var store = _loadStore(); 139 delete store[String(originalAnalysisId)]; 140 try { localStorage.setItem(_LS_KEY, JSON.stringify(store)); } catch (_e) {} 141 } 142 143 function _findCardByPid(pid) { 144 if (!pid || !global.document) return null; 145 if (typeof global._hpCardByPid === 'function') { 146 var found = global._hpCardByPid(pid); 147 if (found) return found; 148 } 149 return global.document.querySelector( 150 '.pc-card[data-pid="' + pid + '"], .hp2-card[data-pid="' + pid + '"], [data-pid="' + pid + '"].upc-anl-card' 151 ); 152 } 153 154 function _attachCard(cardEl, pid) { 155 if (!cardEl || !pid) return; 156 cardEl.setAttribute('data-ca-running', '1'); 157 cardEl.setAttribute('data-ea-running', '1'); 158 cardEl.setAttribute('data-ca-pid', String(pid)); 159 cardEl.classList.add('upc-anl-card', 'upc-anl-card--expanded'); 160 var toggle = cardEl.querySelector('.upc-analyze-toggle'); 161 if (toggle) { 162 toggle.disabled = true; 163 toggle.setAttribute('aria-busy', 'true'); 164 toggle.classList.add('upc-analyze-toggle-busy'); 165 } 166 var prev = cardEl.querySelector('.upc-anl-section--ca'); 167 if (prev) prev.remove(); 168 var sec = global.document.createElement('div'); 169 sec.className = 'upc-anl-section upc-anl-section--expanded upc-anl-section--ca'; 170 sec.setAttribute('data-ca-card-progress', '1'); 171 sec.innerHTML = 172 '<div class="upc-anl-type-badge">' + _t('Comprehensive analysis', 'Ø§ÙØªØÙÙÙ Ø§ÙØ´Ø§Ù Ù') + '</div>' + 173 '<div class="upc-anl-header">' + 174 '<div class="upc-anl-spinner upc-anl-spinner--expanded" aria-hidden="true"></div>' + 175 '<span class="upc-anl-msg-main" data-ca-card-msg="1">' + 176 _phaseMessage('INITIALIZING', 'RUNNING') + 177 '</span>' + 178 '<span class="upc-anl-pct" data-ca-card-pct="1"></span>' + 179 '</div>' + 180 '<p class="upc-anl-exp-hint">â± ' + 181 _t('Takes several minutes on average', 'ÙØ³ØªØºØ±Ù Ù٠اÙÙ ØªÙØ³Ø· عدة Ø¯ÙØ§Ø¦Ù') + 182 '</p>'; 183 cardEl.appendChild(sec); 184 } 185 186 function _updateCard(cardEl, phase, status, freshness, pct) { 187 if (!cardEl) return; 188 var msgEl = cardEl.querySelector('[data-ca-card-msg]'); 189 if (msgEl) msgEl.textContent = _phaseMessage(phase, status, freshness); 190 var pctEl = cardEl.querySelector('[data-ca-card-pct]'); 191 if (pctEl && typeof pct === 'number') pctEl.textContent = Math.round(pct) + '%'; 192 try { 193 global.document.dispatchEvent(new CustomEvent('ca:progress-update', { 194 detail: { 195 pid: cardEl.getAttribute('data-ca-pid') || '', 196 phase: phase || '', 197 status: status || '', 198 msg: _phaseMessage(phase, status), 199 }, 200 })); 201 } catch (_e) {} 202 } 203 204 function _releaseCard(cardEl, pid, outcome) { 205 outcome = outcome || {}; 206 if (cardEl) { 207 var sec = cardEl.querySelector('.upc-anl-section--ca'); 208 if (sec) sec.remove(); 209 cardEl.removeAttribute('data-ca-running'); 210 cardEl.removeAttribute('data-ea-running'); 211 cardEl.removeAttribute('data-analyzing'); 212 cardEl.removeAttribute('data-ca-pid'); 213 cardEl.classList.remove('upc-anl-card', 'upc-anl-card--expanded'); 214 var toggle = cardEl.querySelector('.upc-analyze-toggle'); 215 if (toggle) { 216 toggle.disabled = false;
217 toggle.removeAttribute('aria-busy'); 218 toggle.classList.remove('upc-analyze-toggle-busy', 'upc-act-find-loading'); 219 } 220 } 221 if (pid && typeof global.unlockCardForAnalysis === 'function') { 222 global.unlockCardForAnalysis(String(pid)); 223 } 224 try { 225 global.document.dispatchEvent(new CustomEvent(outcome.failed ? 'ca:job-failed' : 'ca:job-complete', { 226 detail: { pid: pid || '', url: outcome.url || '', stale: !!outcome.stale }, 227 })); 228 } catch (_e) {} 229 } 230 231 function _setBusy(btn, busy) { 232 if (!btn) return; 233 btn.disabled = !!busy; 234 if (busy) btn.setAttribute('aria-busy', 'true'); 235 else btn.removeAttribute('aria-busy'); 236 } 237 238 function _ensureBanner() { 239 var el = document.getElementById('caUxBanner'); 240 if (el) return el; 241 el = document.createElement('div'); 242 el.id = 'caUxBanner'; 243 el.className = 'ea-ux-banner'; 244 el.setAttribute('role', 'status'); 245 el.setAttribute('aria-live', 'polite'); 246 el.innerHTML = 247 '<div class="ea-ux-banner__inner">' + 248 '<div class="ea-ux-banner__icon" id="caUxBannerIcon" aria-hidden="true">ð</div>' + 249 '<div class="ea-ux-banner__body">' + 250 '<div class="ea-ux-banner__type" id="caUxBannerType"></div>' + 251 '<div class="ea-ux-banner__msg" id="caUxBannerMsg"></div>' + 252 '<div class="ea-ux-banner__pct" id="caUxBannerPct"></div>' + 253 '<div class="ea-ux-banner__bar-wrap" id="caUxBannerBarWrap" aria-hidden="true">' + 254 '<div class="ea-ux-banner__bar" id="caUxBannerBar"></div>' + 255 '</div>' + 256 '<div class="ea-ux-banner__stages" id="caUxBannerStages" hidden></div>' + 257 '</div>' + 258 '<button type="button" class="ea-ux-banner__close" id="caUxBannerClose" aria-label="' + 259 _t('Close', 'Ø¥ØºÙØ§Ù') + '">Ã</button>' + 260 '</div>'; 261 document.body.appendChild(el); 262 var closeBtn = document.getElementById('caUxBannerClose'); 263 if (closeBtn) { 264 closeBtn.addEventListener('click', function(ev) { 265 ev.stopPropagation(); 266 el.hidden = true; 267 }); 268 } 269 return el; 270 } 271 272 function _paintBannerBar(pct, failed, stale) { 273 var bar = document.getElementById('caUxBannerBar'); 274 var wrap = document.getElementById('caUxBannerBarWrap'); 275 var pctEl = document.getElementById('caUxBannerPct'); 276 if (pctEl) { 277 pctEl.hidden = !!failed; 278 if (!failed && typeof pct === 'number') pctEl.textContent = Math.round(pct) + '%'; 279 } 280 if (!bar || !wrap) return; 281 wrap.hidden = !!failed; 282 if (failed) return;
283 var safe = Math.max(0, Math.min(100, Math.round(pct || 0))); 284 bar.classList.add('ea-ux-banner__bar--determinate'); 285 bar.style.width = safe + '%'; 286 bar.style.animation = (safe >= 100 && !stale) ? 'none' : ''; 287 } 288 289 function _syncSharedState(originalAnalysisId, data) { 290 data = data || {}; 291 var phase = data.phase || 'INITIALIZING'; 292 var status = data.status || 'RUNNING'; 293 var freshness = data.freshnessStatus || ''; 294 var caId = data.comprehensiveAnalysisId || ''; 295 var msg = _phaseMessage(phase, status, freshness); 296 var icon = _progressIcon(phase, status, freshness); 297 var ready = status === 'READY' && phase === 'PROCESSED_READY'; 298 var failed = status === 'FAILED'; 299 var stale = freshness === 'STALE' && ready; 300 var url = (caId && originalAnalysisId) ? _resultUrl(originalAnalysisId, caId) : ''; 301 var pct = _progressPercent(data, _runtime && _runtime.pct); 302 _runtime = { 303 originalAnalysisId: originalAnalysisId || '', 304 comprehensiveAnalysisId: caId, 305 phase: phase, 306 status: status, 307 freshnessStatus: freshness, 308 msg: msg, 309 icon: icon, 310 url: url, 311 pct: pct, 312 }; 313 var el = _ensureBanner(); 314 el.hidden = false; 315 var typeEl = document.getElementById('caUxBannerType'); 316 var msgEl = document.getElementById('caUxBannerMsg'); 317 var iconEl = document.getElementById('caUxBannerIcon'); 318 if (typeEl) typeEl.textContent = _t('Comprehensive analysis', 'Ø§ÙØªØÙÙÙ Ø§ÙØ´Ø§Ù Ù'); 319 if (msgEl) msgEl.textContent = msg; 320 if (iconEl) iconEl.textContent = icon; 321 _paintBannerBar(pct, failed, stale); 322 if (ready && url && !stale) { 323 el.style = el.style || {}; 324 el.style.cursor = 'pointer'; 325 el.onclick = function(ev) { 326 if (ev.target && ev.target.id === 'caUxBannerClose') return; 327 global.location.href = url; 328 }; 329 } else { 330 el.onclick = null; 331 el.style = el.style || {}; 332 el.style.cursor = ''; 333 } 334 _syncCardFromStatus(originalAnalysisId, phase, status, freshness, pct); 335 if (originalAnalysisId && global.SMQ && typeof global.SMQ.setJobMeta === 'function') { 336 if (typeof global.SMQ.handoffComprehensiveProgress === 'function') { 337 global.SMQ.handoffComprehensiveProgress(originalAnalysisId); 338 } 339 global.SMQ.setJobMeta(originalAnalysisId, { 340 analysisType: 'comprehensive', 341 caProgressOwned: true, 342 eaProgressOwned: false, 343 clearExpandedPct: false, 344 comprehensiveAnalysisId: caId, 345 freshnessStatus: freshness, 346 expandedPhase: failed ? 'failed' : (ready ? 'done' : 'running'), 347 expandedProgressMsg: msg, 348 expandedDisplayPct: pct, 349 displayPct: pct, 350 targetPct: pct, 351 progress: pct, 352 status: failed ? 'failed' : (ready ? 'done' : 'running'), 353 url: url || undefined, 354 }); 355 } 356 try { 357 global.document.dispatchEvent(new CustomEvent('ca:state', { detail: _runtime })); 358 global.document.dispatchEvent(new CustomEvent('ca:progress-update', { 359 detail: { 360 pid: (_cardByOriginal[originalAnalysisId] || {}).pid || '', 361 originalAnalysisId: originalAnalysisId || '', 362 comprehensiveAnalysisId: caId, 363 phase: phase, 364 status: status, 365 freshnessStatus: freshness, 366 msg: msg, 367 }, 368 })); 369 } catch (_e) {} 370 return _runtime; 371 } 372 373 function _showProgress(phase, status, extras) { 374 extras = extras || {}; 375 _syncSharedState(extras.originalAnalysisId || extras.jobId || '', { 376 phase: phase, 377 status: status, 378 freshnessStatus: extras.freshnessStatus || '', 379 comprehensiveAnalysisId: extras.comprehensiveAnalysisId || extras.caId || '', 380 }); 381 } 382 383 function _showFailed(onRetry, originalAnalysisId) { 384 _syncSharedState(originalAnalysisId || '', { phase: 'FAILED', status: 'FAILED' }); 385 var el = _ensureBanner(); 386 var actions = document.getElementById('caUxBannerStages'); 387 if (!actions) return; 388 actions.hidden = false; 389 actions.innerHTML = '<button type="button" class="ea-ux-btn ea-ux-btn--secondary" id="caUxRetry">' + 390 _t('Try again', 'إعادة اÙÙ ØØ§ÙÙØ©') + '</button>'; 391 var retry = document.getElementById('caUxRetry'); 392 if (retry && typeof onRetry === 'function') { 393 retry.addEventListener('click', function() { 394 el.hidden = true; 395 onRetry(); 396 }); 397 } 398 } 399 400 function _resultUrl(originalAnalysisId, comprehensiveAnalysisId) { 401 return '/analysis-summary/' + encodeURIComponent(originalAnalysisId)
402 + '?comprehensive_analysis_id=' + encodeURIComponent(comprehensiveAnalysisId); 403 } 404 405 function _parseBody(json) { 406 if (!json || json.ok === false) return null; 407 return json.data || json; 408 } 409 410 function _syncCardFromStatus(originalAnalysisId, phase, status, freshness, pct) { 411 var ctx = _cardByOriginal[originalAnalysisId]; 412 if (ctx && ctx.el) _updateCard(ctx.el, phase, status, freshness, pct); 413 } 414 415 function _bindCardWhenReady(originalAnalysisId, pid) { 416 if (!pid) return; 417 function tryBind() { 418 var card = _findCardByPid(pid); 419 if (!card) return false; 420 _cardByOriginal[originalAnalysisId] = { el: card, pid: String(pid) }; 421 if (card.getAttribute('data-ca-running') !== '1') _attachCard(card, pid); 422 return true; 423 } 424 if (tryBind() || !global.document) return; 425 var root = global.document.getElementById('hpContent') || global.document.body; 426 if (!root || !global.MutationObserver) return; 427 var obs = new MutationObserver(function() { 428 if (tryBind()) obs.disconnect(); 429 }); 430 obs.observe(root, { childList: true, subtree: true }); 431 setTimeout(function() { try { obs.disconnect(); } catch (_e) {} }, 60000); 432 } 433 434 function _isStillRunning(data) { 435 if (!data) return false; 436 if (data.status === 'FAILED') return false; 437 if (data.status === 'READY' && data.phase === 'PROCESSED_READY') return false; 438 return data.status === 'PENDING' || data.status === 'RUNNING' 439 || (data.status === 'READY' && data.phase !== 'PROCESSED_READY'); 440 } 441 442 function _pollStatus(comprehensiveAnalysisId, originalAnalysisId, btn) { 443 if (_pollById[comprehensiveAnalysisId]) return _pollById[comprehensiveAnalysisId]; 444 var promise = new Promise(function(resolve, reject) { 445 var attempts = 0; 446 function tick() { 447 attempts += 1;
448 fetch('/api/comprehensive-analysis/customer/' + encodeURIComponent(comprehensiveAnalysisId) + '/status', { 449 cache: 'no-store', 450 }) 451 .then(function(r) { return r.json(); }) 452 .then(function(body) { 453 var data = _parseBody(body); 454 if (!data) { 455 if (attempts > 900) return reject(new Error('status_failed')); 456 return setTimeout(tick, 2000); 457 } 458 _syncSharedState(originalAnalysisId, data); 459 if (data.status === 'FAILED') return resolve(data); 460 if (data.status === 'READY' && data.phase === 'PROCESSED_READY') return resolve(data); 461 if (attempts > 900) return reject(new Error('timeout')); 462 setTimeout(tick, 2500); 463 }) 464 .catch(function() { 465 if (attempts > 900) return reject(new Error('timeout')); 466 setTimeout(tick, 3000); 467 }); 468 } 469 tick(); 470 }).finally(function() { 471 delete _pollById[comprehensiveAnalysisId]; 472 _setBusy(btn, false); 473 }); 474 _pollById[comprehensiveAnalysisId] = promise; 475 return promise; 476 } 477 478 function _handleTerminal(data, originalAnalysisId, btn) { 479 if (!data) return; 480 var ctx = _cardByOriginal[originalAnalysisId] || {}; 481 var cardEl = ctx.el || null; 482 var pid = ctx.pid || ''; 483 if (data.status === 'FAILED') { 484 _releaseCard(cardEl, pid, { failed: true }); 485 delete _cardByOriginal[originalAnalysisId]; 486 _clearRun(originalAnalysisId); 487 _showFailed(function() { startForJob(originalAnalysisId, { button: btn, force: true, cardEl: cardEl, pid: pid }); }, originalAnalysisId); 488 return; 489 } 490 if (data.status === 'READY' && data.phase === 'PROCESSED_READY') { 491 _releaseCard(cardEl, pid, { stale: data.freshnessStatus === 'STALE', url: _resultUrl(originalAnalysisId, data.comprehensiveAnalysisId) }); 492 delete _cardByOriginal[originalAnalysisId]; 493 } 494 if (data.freshnessStatus === 'STALE') { 495 _syncSharedState(originalAnalysisId, data); 496 return; 497 } 498 if (data.freshnessStatus === 'UNKNOWN' && data.processedReady) { 499 _syncSharedState(originalAnalysisId, { 500 phase: 'PROCESSING', 501 status: 'RUNNING', 502 comprehensiveAnalysisId: data.comprehensiveAnalysisId, 503 freshnessStatus: data.freshnessStatus, 504 }); 505 return; 506 } 507 if (data.processedReady && data.freshnessStatus !== 'STALE') { 508 var url = _resultUrl(originalAnalysisId, data.comprehensiveAnalysisId); 509 _clearRun(originalAnalysisId); 510 _syncSharedState(originalAnalysisId, data); 511 setTimeout(function() { global.location.href = url; }, 600); 512 } 513 } 514 515 function startForJob(originalAnalysisId, opts) { 516 opts = opts || {}; 517 var jobId = String(originalAnalysisId || '').trim(); 518 var btn = opts.button || null; 519 var pid = opts.pid ? String(opts.pid) : ''; 520 var cardEl = opts.cardEl || null; 521 if (cardEl && pid) { 522 _cardByOriginal[jobId] = { el: cardEl, pid: pid }; 523 _attachCard(cardEl, pid); 524 } 525 if (!jobId) return Promise.resolve(null); 526 if (_inFlightByOriginal[jobId] && !opts.force) { 527 _setBusy(btn, true); 528 return _inFlightByOriginal[jobId]; 529 } 530 531 _setBusy(btn, true); 532 _syncSharedState(jobId, { phase: 'INITIALIZING', status: 'RUNNING' }); 533 534 function persistAndContinue(data, caId) { 535 var keepPid = pid || ((_savedEntry(jobId) || {}).pid || ''); 536 _saveRun(jobId, caId, keepPid); 537 if (data.status === 'FAILED') return data; 538 if (data.status === 'READY' && data.phase === 'PROCESSED_READY') return data; 539 return _pollStatus(caId, jobId, btn); 540 } 541 542 var saved = !opts.force ? _savedRun(jobId) : ''; 543 var work = (saved
544 ? fetch('/api/comprehensive-analysis/customer/' + encodeURIComponent(saved) + '/status', { cache: 'no-store' }) 545 .then(function(r) { return r.json(); }) 546 .then(function(body) { 547 var data = _parseBody(body); 548 if (!data) throw new Error('resume_failed'); 549 _syncSharedState(jobId, data); 550 return persistAndContinue(data, saved); 551 }) 552 : fetch('/api/comprehensive-analysis/customer/start', { 553 method: 'POST', 554 headers: { 'Content-Type': 'application/json' }, 555 body: JSON.stringify({ originalAnalysisId: jobId }), 556 }) 557 .then(function(r) { return r.json(); }) 558 .then(function(body) { 559 if (!body || body.ok === false) throw new Error('start_failed'); 560 var data = body.data || {}; 561 var caId = data.comprehensiveAnalysisId; 562 if (!caId) throw new Error('start_failed'); 563 _syncSharedState(jobId, data); 564 return persistAndContinue(data, caId); 565 }) 566 ) 567 .then(function(data) { 568 _handleTerminal(data, jobId, btn); 569 return data; 570 }) 571 .catch(function() { 572 _releaseCard(cardEl, pid, { failed: true }); 573 delete _cardByOriginal[jobId]; 574 _showFailed(function() { startForJob(jobId, { button: btn, force: true, cardEl: cardEl, pid: pid }); }, jobId); 575 return null; 576 }) 577 .finally(function() { 578 delete _inFlightByOriginal[jobId]; 579 _setBusy(btn, false); 580 }); 581 582 _inFlightByOriginal[jobId] = work; 583 return work; 584 } 585 586 async function startFromHomepage(candidate, sourceType, cardEl) { 587 if (!candidate) return; 588 var pid = String(candidate.product_id || ''); 589 var btn = cardEl && (cardEl.querySelector('.upc-analyze-toggle') || cardEl.querySelector('.upc-act-expanded')); 590 if (typeof global.lockCardForAnalysis === 'function') { 591 global.lockCardForAnalysis(pid); 592 } 593 if (cardEl && pid) { 594 _cardByOriginal['pid:' + pid] = { el: cardEl, pid: pid }; 595 _attachCard(cardEl, pid); 596 } 597 _syncSharedState('', { phase: 'INITIALIZING', status: 'RUNNING' }); 598 var jobId = null; 599 var result = null; 600 try { 601 if (typeof global.startAnalysisJob === 'function') { 602 result = await global.startAnalysisJob(candidate, sourceType === 'homepage-expanded' ? 'homepage-comprehensive' : (sourceType || 'homepage-comprehensive')); 603 jobId = result && result.jobId; 604 } 605 if (!jobId) throw new Error('no_job'); 606 if (cardEl && pid) { 607 delete _cardByOriginal['pid:' + pid]; 608 _cardByOriginal[jobId] = { el: cardEl, pid: pid }; 609 } 610 _syncSharedState(jobId, { phase: 'INITIALIZING', status: 'RUNNING' }); 611 if (!result || !result.cacheHit) { 612 await _waitJobComplete(jobId); 613 } 614 return startForJob(jobId, { button: btn, cardEl: cardEl, pid: pid }); 615 } catch (_e) { 616 _releaseCard(cardEl, pid, { failed: true }); 617 delete _cardByOriginal['pid:' + pid]; 618 if (jobId) delete _cardByOriginal[jobId]; 619 _showFailed(function() { startFromHomepage(candidate, sourceType, cardEl); }, jobId || ''); 620 } 621 } 622 623 function restoreHomepageRuns() { 624 var init = global.__ASP_INIT__ || {}; 625 if (init.JOB_ID) return; 626 var store = _loadStore(); 627 Object.keys(store).forEach(function(jobId) { 628 var entry = _savedEntry(jobId); 629 if (!entry || !entry.ca) return;
630 fetch('/api/comprehensive-analysis/customer/' + encodeURIComponent(entry.ca) + '/status', { cache: 'no-store' }) 631 .then(function(r) { return r.json(); }) 632 .then(function(body) { 633 var data = _parseBody(body); 634 if (!data) return; 635 if (!_isStillRunning(data)) return; 636 _syncSharedState(jobId, data); 637 if (entry.pid) _bindCardWhenReady(jobId, entry.pid); 638 if (_inFlightByOriginal[jobId]) return; 639 var work = _pollStatus(entry.ca, jobId, null).then(function(done) { 640 _handleTerminal(done, jobId, null); 641 return done; 642 }); 643 _inFlightByOriginal[jobId] = work; 644 }) 645 .catch(function() {}); 646 }); 647 } 648 649 function _waitJobComplete(jobId) { 650 return new Promise(function(resolve, reject) { 651 var attempts = 0; 652 function tick() { 653 attempts += 1; 654 fetch('/api/analysis/' + encodeURIComponent(jobId) + '/data', { cache: 'no-store' }) 655 .then(function(r) { return r.json(); }) 656 .then(function(body) { 657 if (body && body.ok && body.job && body.job.status === 'complete') return resolve(body.job); 658 if (body && body.job && (body.job.status === 'error' || body.job.status === 'cancelled')) { 659 return reject(new Error(body.job.status)); 660 } 661 if (attempts > 900) return reject(new Error('timeout')); 662 setTimeout(tick, 2000); 663 }) 664 .catch(function(err) { 665 if (attempts > 900) return reject(err); 666 setTimeout(tick, 2500); 667 }); 668 } 669 tick(); 670 }); 671 } 672 673 function renderCtaHtml() { 674 var title = _t('Comprehensive analysis', 'Ø§ÙØªØÙÙÙ Ø§ÙØ´Ø§Ù Ù'); 675 var hint = _t('Takes several minutes on average', 'ÙØ³ØªØºØ±Ù Ù٠اÙÙ ØªÙØ³Ø· عدة Ø¯ÙØ§Ø¦Ù'); 676 return ( 677 '<section class="ea-ux-cta ea-ux-cta--top" aria-label="' + title + '">' + 678 '<div class="ea-ux-cta__hd">' + title + '</div>' + 679 '<p class="ea-ux-cta__hint">â± ' + hint + '</p>' + 680 '<button type="button" class="ea-ux-btn ea-ux-btn--primary ca-ux-start-btn">' + title + '</button>' + 681 '</section>' 682 ); 683 } 684 685 function bindAnalysisPageCtas() { 686 var init = global.__ASP_INIT__ || {}; 687 if (init.COMPREHENSIVE_ANALYSIS_ID || init.EXPANDED_SEARCH_ID) return; 688 var jobId = init.JOB_ID || ''; 689 if (!jobId) return; 690 691 var topHost = document.getElementById('eaUxCtaTop'); 692 var bottomHost = document.getElementById('eaUxCtaBottom'); 693 if (topHost && !topHost.innerHTML) topHost.innerHTML = renderCtaHtml(); 694 if (bottomHost && !bottomHost.innerHTML) bottomHost.innerHTML = renderCtaHtml(); 695 696 document.querySelectorAll('.ca-ux-start-btn').forEach(function(btn) { 697 if (btn.getAttribute('data-ca-bound') === '1') return; 698 btn.setAttribute('data-ca-bound', '1'); 699 btn.addEventListener('click', function() { 700 if (btn.disabled) return; 701 startForJob(jobId, { button: btn }); 702 }); 703 }); 704 705 var saved = _savedRun(jobId); 706 if (saved) {
707 fetch('/api/comprehensive-analysis/customer/' + encodeURIComponent(saved) + '/status', { cache: 'no-store' }) 708 .then(function(r) { return r.json(); }) 709 .then(function(body) { 710 var data = _parseBody(body); 711 if (!data) return; 712 if (_isStillRunning(data)) { 713 document.querySelectorAll('.ca-ux-start-btn').forEach(function(b) { _setBusy(b, true); }); 714 _syncSharedState(jobId, data); 715 if (!_inFlightByOriginal[jobId]) { 716 _inFlightByOriginal[jobId] = _pollStatus(saved, jobId, null).then(function(done) { 717 _handleTerminal(done, jobId, null); 718 return done; 719 }); 720 } 721 } 722 }) 723 .catch(function() {}); 724 } 725 } 726 727 global.ComprehensiveAnalysisUX = { 728 startForJob: startForJob, 729 startFromHomepage: startFromHomepage, 730 bindAnalysisPageCtas: bindAnalysisPageCtas, 731 restoreHomepageRuns: restoreHomepageRuns, 732 renderCtaHtml: renderCtaHtml, 733 phaseMessage: _phaseMessage, 734 stageIndex: _stageIndex, 735 progressPercent: _progressPercent, 736 getRuntime: function() { return _runtime; }, 737 }; 738 739 if (global.document) { 740 if (global.document.readyState === 'loading') { 741 global.document.addEventListener('DOMContentLoaded', restoreHomepageRuns); 742 } else { 743 setTimeout(restoreHomepageRuns, 0); 744 } 745 } 746})(window);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.