1/* âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 2 IMAGE SEARCH DIAGNOSTICS & TELEMETRY (Phase 6) 3 âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 4 COMPLETELY ISOLATED. This file does not change what any other module 5 does, returns, or how long it takes â it only OBSERVES already-existing 6 public functions/objects by wrapping them (classic decorator pattern): 7 every wrapper calls straight through to the original implementation 8 with the exact same arguments and returns/throws the exact same 9 result/error. Nothing here can alter the real Image Search flow. 10 11 ZERO modification was made to ANY other file for this phase â not even 12 an additive line â Search Pipeline, Image Intake, Validation, Quality, 13 Optimization, and Image Search UX (Phase 1â5, all files) are all 14 byte-for-byte untouched. This module attaches itself purely from the 15 outside, after those modules have already defined their public globals 16 (ImageIntake, ImageValidation, ImageQuality, window.fetch, 17 _compressImageForSearch, renderImageSearchResults, showAlert). 18 19 Disabled by default (production users see zero difference â every 20 wrapper's FIRST line is an isEnabled() check that, when false, calls 21 straight through with no extra work at all â same function call, same 22 number of ticks, no new Promises allocated). Enable for local debugging: 23 localStorage.setItem('sm_image_diag', '1') (persists) 24 ...or open the page with ?diag=1 / ?imageDiag=1 (this tab only) 25 ...or window.ImageSearchDiagnostics.enable() 26 27 Output: window.ImageSearchDiagnostics â no UI, no modal, no console 28 spam, no alerts. Pure read API for devtools / a future admin dashboard. 29 âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 30 31(function (global) { 32 'use strict'; 33 34 var MAX_TRACES = 50; 35 var _traces = []; 36 var _activeTraceId = null; 37 var _pendingSource = 'unknown'; 38 39 // ââ Enable / disable (debug-only; production default = OFF) âââââââââââ 40 41 function _isEnabled() { 42 try { 43 if (global.__SM_IMAGE_DIAG_FORCE__ === true) return true; 44 if (global.__SM_IMAGE_DIAG_FORCE__ === false) return false; 45 if (window.localStorage && window.localStorage.getItem('sm_image_diag') === '1') return true; 46 var qs = new URLSearchParams(window.location.search || ''); 47 if (qs.get('diag') === '1' || qs.get('imageDiag') === '1') return true; 48 } catch (_) { /* ignore â default to disabled on any error */ } 49 return false; 50 } 51 52 function enable() { try { window.localStorage.setItem('sm_image_diag', '1'); } catch (_) {} } 53 function disable() { try { window.localStorage.removeItem('sm_image_diag'); } catch (_) {} } 54 55 // ââ Trace storage (bounded ring buffer, in-memory only â no network, 56 // no localStorage persistence of trace CONTENT, no server calls) ââââ 57 58 function _uuid() { 59 return 'trc_' + Date.now().toString(36) + '_' + Math.random().toString(36).slice(2, 10); 60 } 61 62 function _newTrace(sourceHint) { 63 var t = { 64 traceId: _uuid(), 65 timestamp: new Date().toISOString(), 66 browser: (navigator.userAgent || ''), 67 platform: (navigator.userAgentData && navigator.userAgentData.platform) || navigator.platform || '', 68 imageSource: sourceHint || 'unknown', 69 stages: {}, // stageKey -> { start, end, duration, status, errorCode, data } 70 original: {}, 71 normalized: {}, 72 finalStatus: 'in_progress', 73 failedStage: null, 74 failureErrorCode: null, 75 startedAt: performance.now(), 76 totalDuration: null, 77 }; 78 _traces.push(t); 79 if (_traces.length > MAX_TRACES) _traces.shift(); 80 _activeTraceId = t.traceId; 81 return t; 82 } 83 84 function _find(traceId) { 85 for (var i = _traces.length - 1; i >= 0; i--) if (_traces[i].traceId === traceId) return _traces[i]; 86 return null; 87 } 88 89 function _stageStart(traceId, stageKey) { 90 var t = _find(traceId); 91 if (!t) return; 92 t.stages[stageKey] = t.stages[stageKey] || {}; 93 t.stages[stageKey].start = performance.now(); 94 t.stages[stageKey].status = 'running'; 95 } 96 97 function _stageEnd(traceId, stageKey, opts) { 98 var t = _find(traceId); 99 if (!t) return; 100 opts = opts || {}; 101 var s = t.stages[stageKey] = t.stages[stageKey] || {}; 102 s.end = performance.now(); 103 s.start = (typeof s.start === 'number') ? s.start : s.end; 104 s.duration = Math.round((s.end - s.start) * 100) / 100; 105 s.status = opts.status || 'ok'; 106 if (opts.errorCode) s.errorCode = opts.errorCode; 107 if (opts.data) s.data = opts.data; 108 if (s.status === 'fail' && t.finalStatus === 'in_progress') { 109 t.finalStatus = 'failed'; 110 t.failedStage = stageKey; 111 t.failureErrorCode = opts.errorCode || 'UNKNOWN_ERROR'; 112 t.totalDuration = Math.round((performance.now() - t.startedAt) * 100) / 100; 113 } 114 } 115 116 function _finishSuccess(traceId) { 117 var t = _find(traceId); 118 if (!t || t.finalStatus !== 'in_progress') return; 119 t.finalStatus = 'success'; 120 t.totalDuration = Math.round((performance.now() - t.startedAt) * 100) / 100; 121 } 122 123 // ââ Public read API âââââââââââââââââââââââââââââââââââââââââââââââââââââ 124 125 function getTrace(traceId) { return _find(traceId); } 126 function getAllTraces() { return _traces.slice(); } 127 function getLastTrace() { return _traces.length ? _traces[_traces.length - 1] : null; } 128 function clear() { _traces.length = 0; _activeTraceId = null; } 129 130 // ââ Source detection (independent, passive listeners â does not touch 131 // or depend on homepage-core.js's own listeners in any way; multiple 132 // listeners on the same element/event are standard DOM behavior and 133 // never conflict) âââââââââââââââââââââââââââââââââââââââââââââââââââââ 134 135 function _attachSourceListeners() { 136 var fileInput = document.getElementById('imgFileInput'); 137 var dropZone = document.getElementById('imgDropZone'); 138 if (fileInput) { 139 fileInput.addEventListener('change', function (e) { 140 if (e.target.files && e.target.files[0]) _pendingSource = 'file-picker'; 141 }); 142 } 143 if (dropZone) { 144 dropZone.addEventListener('drop', function () { _pendingSource = 'drag-drop'; }); 145 } 146 document.addEventListener('paste', function (e) { 147 var items = e.clipboardData && e.clipboardData.items; 148 if (!items) return; 149 for (var i = 0; i < items.length; i++) { 150 if (items[i].type && items[i].type.indexOf('image/') === 0) { _pendingSource = 'clipboard'; break; } 151 } 152 }); 153 } 154 155 // ââ Wrappers â each is a pure decorator: same args in, same result out, 156 // only side-channel timing/data recorded to the active trace ââââââââ 157 158 function _wrapIntake() { 159 if (!global.ImageIntake || typeof global.ImageIntake.normalize !== 'function') return; 160 if (global.ImageIntake.normalize.__smDiagWrapped) return; 161 var _orig = global.ImageIntake.normalize; 162 var wrapped = function (file) { 163 if (!_isEnabled()) return _orig.apply(this, arguments); 164 var t = _newTrace(_pendingSource); 165 t.original = { format: (file && file.type) || '', size: (file && file.size) || 0 }; 166 var traceId = t.traceId; 167 _stageStart(traceId, 'intake'); 168 return _orig.apply(this, arguments).then( 169 function (normalized) { 170 var tr = _find(traceId); 171 if (tr) tr.normalized = { format: normalized.type || '', size: normalized.size || 0 }; 172 _stageEnd(traceId, 'intake', { status: 'ok' }); 173 return normalized; 174 }, 175 function (err) { 176 _stageEnd(traceId, 'intake', { status: 'fail', errorCode: (err && err.code) || 'INTAKE_ERROR' }); 177 throw err; 178 } 179 ); 180 }; 181 wrapped.__smDiagWrapped = true; 182 global.ImageIntake.normalize = wrapped; 183 } 184 185 function _wrapValidation() { 186 if (!global.ImageValidation || typeof global.ImageValidation.validate !== 'function') return; 187 if (global.ImageValidation.validate.__smDiagWrapped) return; 188 var _orig = global.ImageValidation.validate; 189 var wrapped = function (file) { 190 if (!_isEnabled()) return _orig.apply(this, arguments); 191 var traceId = _activeTraceId; 192 _stageStart(traceId, 'validation'); 193 return _orig.apply(this, arguments).then(function (result) { 194 var tr = _find(traceId); 195 if (tr && result && result.ok) { 196 tr.normalized.width = result.width; 197 tr.normalized.height = result.height; 198 } 199 _stageEnd(traceId, 'validation', { 200 status: result && result.ok ? 'ok' : 'fail', 201 errorCode: result && !result.ok ? result.code : undefined, 202 data: { code: result && result.code }, 203 }); 204 return result; 205 }); 206 }; 207 wrapped.__smDiagWrapped = true; 208 global.ImageValidation.validate = wrapped; 209 } 210 211 function _wrapQuality() { 212 if (!global.ImageQuality || typeof global.ImageQuality.assess !== 'function') return; 213 if (global.ImageQuality.assess.__smDiagWrapped) return; 214 var _orig = global.ImageQuality.assess; 215 var wrapped = function (file) { 216 if (!_isEnabled()) return _orig.apply(this, arguments); 217 var traceId = _activeTraceId; 218 _stageStart(traceId, 'quality'); 219 return _orig.apply(this, arguments).then(function (result) { 220 var m = (result && result.metrics) || {}; 221 _stageEnd(traceId, 'quality', { 222 status: result && result.ok ? 'ok' : 'fail', 223 errorCode: result && !result.ok ? result.code : undefined, 224 data: { 225 code: result && result.code, 226 blurScore: m.blurVariance, 227 brightnessScore: m.mean, 228 contrastScore: m.stdDev, 229 entropyScore: m.entropy, 230 coverageScore: m.coverageRatio, 231 noiseScore: m.noiseSigma, 232 }, 233 }); 234 return result; 235 }); 236 }; 237 wrapped.__smDiagWrapped = true; 238 global.ImageQuality.assess = wrapped; 239 } 240 241 function _wrapCompression() { 242 if (typeof global._compressImageForSearch !== 'function') return; 243 if (global._compressImageForSearch.__smDiagWrapped) return; 244 var _orig = global._compressImageForSearch; 245 var wrapped = function (file) { 246 if (!_isEnabled()) return _orig.apply(this, arguments); 247 var traceId = _activeTraceId; 248 var originalSize = (file && file.size) || 0; 249 _stageStart(traceId, 'optimization'); 250 return _orig.apply(this, arguments).then( 251 function (b64) { 252 var finalSize = b64 ? Math.round(b64.length * 0.75) : 0; // base64 â approx raw bytes 253 _stageEnd(traceId, 'optimization', { 254 status: 'ok', 255 data: { 256 finalJpegSize: finalSize, 257 compressionRatio: (originalSize > 0 && finalSize > 0) 258 ? Math.round((originalSize / finalSize) * 100) / 100 : null, 259 }, 260 }); 261 return b64; 262 }, 263 function (err) { 264 _stageEnd(traceId, 'optimization', { status: 'fail', errorCode: 'OPTIMIZATION_ERROR' }); 265 throw err; 266 } 267 ); 268 }; 269 wrapped.__smDiagWrapped = true; 270 global._compressImageForSearch = wrapped; 271 } 272 273 function _wrapFetch() { 274 if (typeof global.fetch !== 'function' || global.fetch.__smDiagWrapped) return; 275 var _orig = global.fetch; 276 var wrapped = function (input, init) { 277 if (!_isEnabled()) return _orig.apply(this, arguments); 278 var url = (typeof input === 'string') ? input : ((input && input.url) || ''); 279 var isImageSearch = url.indexOf('/api/image-search') === 0 && url.indexOf('/api/image-search-more') !== 0; 280 if (!isImageSearch) return _orig.apply(this, arguments); 281 282 var traceId = _activeTraceId; 283 // "Uploading" and "AliExpress searching" share one opaque fetch() 284 // round trip (no upload-progress API without switching to XHR, which 285 // would mean touching AliExpress Integration â forbidden). Both 286 // stages are recorded against the same real measured window, 287 // documented exactly like Phase 5's identical, honest finding. 288 _stageStart(traceId, 'upload'); 289 _stageStart(traceId, 'search'); 290 return _orig.apply(this, arguments).then( 291 function (res) { 292 _stageEnd(traceId, 'upload', { status: 'ok' }); 293 try { 294 // .clone() so the REAL response body stream is left completely 295 // untouched for the actual caller (startImageSearch()) to read. 296 res.clone().json().then(function (data) { 297 _stageEnd(traceId, 'search', { 298 status: data && data.ok ? 'ok' : 'fail', 299 errorCode: data && !data.ok ? (data.code || 'SEARCH_ERROR') : undefined, 300 data: { candidateCount: (data && data.candidates && data.candidates.length) || 0 }, 301 }); 302 }).catch(function () { 303 _stageEnd(traceId, 'search', { status: 'ok' }); 304 }); 305 } catch (_) { 306 _stageEnd(traceId, 'search', { status: 'ok' }); 307 } 308 return res; 309 }, 310 function (err) { 311 _stageEnd(traceId, 'upload', { status: 'fail', errorCode: 'NETWORK_E
311RROR' }); 312 _stageEnd(traceId, 'search', { status: 'fail', errorCode: 'NETWORK_ERROR' }); 313 throw err; 314 } 315 ); 316 }; 317 wrapped.__smDiagWrapped = true; 318 global.fetch = wrapped; 319 } 320 321 function _wrapRender() { 322 if (typeof global.renderImageSearchResults !== 'function') return; 323 if (global.renderImageSearchResults.__smDiagWrapped) return; 324 var _orig = global.renderImageSearchResults; 325 var wrapped = function (data, meta, opts) { 326 if (!_isEnabled()) return _orig.apply(this, arguments); 327 var traceId = _activeTraceId; 328 _stageStart(traceId, 'rendering'); 329 var ret = _orig.apply(this, arguments); 330 _stageEnd(traceId, 'rendering', { status: 'ok' }); 331 _finishSuccess(traceId); 332 return ret; 333 }; 334 wrapped.__smDiagWrapped = true; 335 global.renderImageSearchResults = wrapped; 336 } 337 338 function _wrapAlertSafetyNet() { 339 // Safety net only: if a failure occurs through a code path this file 340 // does not directly instrument (e.g. handleImageFile()'s own separate, 341 // pre-existing internal dimension check), the trace is still marked 342 // failed instead of being silently left "in_progress" forever. This 343 // never changes what showAlert displays â purely observes it. 344 if (typeof global.showAlert !== 'function' || global.showAlert.__smDiagWrapped) return; 345 var _orig = global.showAlert; 346 var wrapped = function (type, title, desc) { 347 if (_isEnabled() && type === 'error' && _activeTraceId) { 348 var t = _find(_activeTraceId); 349 if (t && t.finalStatus === 'in_progress') { 350 t.finalStatus = 'failed'; 351 t.failedStage = t.failedStage || 'unknown'; 352 t.failureErrorCode = t.failureErrorCode || (title || 'UNKNOWN_ERROR'); 353 t.totalDuration = Math.round((performance.now() - t.startedAt) * 100) / 100; 354 } 355 } 356 return _orig.apply(this, arguments); 357 }; 358 wrapped.__smDiagWrapped = true; 359 global.showAlert = wrapped; 360 } 361 362 function _install() { 363 _wrapIntake(); 364 _wrapValidation(); 365 _wrapQuality(); 366 _wrapCompression(); 367 _wrapFetch(); 368 _wrapRender(); 369 _wrapAlertSafetyNet(); 370 } 371 372 if (document.readyState === 'loading') { 373 document.addEventListener('DOMContentLoaded', _attachSourceListeners); 374 } else { 375 _attachSourceListeners(); 376 } 377 378 // This script tag is placed after every other Image Search module's tag 379 // (Phases 1â5 + homepage-image.js) in server.js, so all of the globals 380 // above already exist by the time this deferred script runs. A single 381 // install pass is therefore sufficient; a best-effort retry on the first 382 // user interaction is kept only as a defensive safety net for the rare 383 // fallback lazy-load path in homepage-core.js (_ensureImageSearch). 384 _install(); 385 document.addEventListener('change', _install, { capture: true }); 386 document.addEventListener('drop', _install, { capture: true }); 387 388 global.ImageSearchDiagnostics = { 389 isEnabled: _isEnabled, 390 enable: enable, 391 disable: disable, 392 getTrace: getTrace, 393 getAllTraces: getAllTraces, 394 getLastTrace: getLastTrace, 395 clear: clear, 396 }; 397})(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.