Some checks failed
Caterium QA / qa (push) Failing after 8m8s
Clients: every page load re-sent all clients (72 requests for 18 clients) because pushAll ran from several startup events at once, never skipped unchanged clients, and overlapping saves of one client read the same stale version, so ~40% ended in 409 conflicts. The payload also carried a fresh updatedAt, so even identical re-sends bumped the server version and wrote a change event, which made other devices' next save conflict too. - Remember what the server holds per client (content fingerprint, scoped to the workspace) and skip unchanged clients; seed it from the server snapshot so a device that is already in sync sends nothing. - Serialise saves per client and make pushAll single-flight. - Load the server snapshot before the startup push instead of racing it. - Drop the volatile updatedAt from the payload (server keeps updated_at). Error log: a record the server refuses (Access denied for a workspace the user is not in) stayed in the IndexedDB queue forever, was re-sent on every flush and could block newer records behind it. Records from another workspace are now dropped, others after 3 attempts. Adds tests/client-sync-v1780.mjs (fake server enforcing the SQL conflict rule; fails on the old module) to test:static, and bumps the cache-busting versions of performance.js / app-runtime.js. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
77 lines
14 KiB
JavaScript
77 lines
14 KiB
JavaScript
(()=>{
|
||
'use strict';
|
||
const VERSION='17.7.3';
|
||
const RELEASE='20260921-client-sync';
|
||
|
||
const hasStoredSession=()=>{try{return Object.keys(localStorage).some(k=>/^sb-.*-auth-token$/i.test(k)&&String(localStorage.getItem(k)||'').length>20)}catch(_){return false}};
|
||
function installAuthBoot(){
|
||
if(hasStoredSession()||document.getElementById('cateriumAuthBootV1776'))return;
|
||
const style=document.createElement('style');style.id='caterium-auth-boot-v1776-style';style.textContent=`
|
||
#cateriumAuthBootV1776{position:fixed;inset:0;z-index:30000;overflow:hidden;display:grid;place-items:center;padding:clamp(32px,6vh,78px) clamp(24px,8vw,120px);background:radial-gradient(circle at 18% 18%,rgba(255,255,255,.92),transparent 34%),radial-gradient(circle at 77% 22%,rgba(255,255,255,.5),transparent 28%),linear-gradient(135deg,#f8f4ec 0%,#f2ece1 56%,#f7f3eb 100%);color:#282621;transition:opacity .18s ease}
|
||
#cateriumAuthBootV1776.leave{opacity:0;pointer-events:none}
|
||
#cateriumAuthBootV1776:before{content:'C';position:fixed;z-index:0;left:-7vw;top:49%;transform:translateY(-50%);font:400 min(72vw,860px)/.72 Georgia,'Times New Roman',serif;color:rgba(73,67,59,.032)}
|
||
#cateriumAuthBootV1776 .cab-wrap{position:relative;z-index:2;width:min(790px,100%);padding:0 0 58px}
|
||
#cateriumAuthBootV1776 .cab-brand{display:flex;align-items:center;gap:13px;margin:0 0 clamp(46px,7vh,88px)}
|
||
#cateriumAuthBootV1776 .cab-mark{display:block;width:62px;height:62px;object-fit:contain;flex:0 0 62px}
|
||
#cateriumAuthBootV1776 .cab-word{font:500 36px/1 Georgia,'Times New Roman',serif;color:#26231f;letter-spacing:-.02em}
|
||
#cateriumAuthBootV1776 h1{margin:0;max-width:720px;color:#24211d;font:500 clamp(48px,5.2vw,76px)/.99 Georgia,'Times New Roman',serif;letter-spacing:-.045em}
|
||
#cateriumAuthBootV1776 p{margin:18px 0 0;color:#8e887f;font:400 clamp(16px,1.55vw,21px)/1.45 Arial,sans-serif}
|
||
#cateriumAuthBootV1776 .cab-note{position:fixed;left:42px;bottom:31px;color:#9d968d;font:9px/1 Arial,sans-serif;letter-spacing:.31em;text-transform:uppercase;text-shadow:none}
|
||
@media(max-width:900px){#cateriumAuthBootV1776{padding:34px 28px 70px;place-items:start center}#cateriumAuthBootV1776 .cab-wrap{width:min(720px,100%);padding-top:12px}}
|
||
@media(max-width:620px){#cateriumAuthBootV1776{min-height:100dvh;padding:25px 18px 50px;place-items:start center}#cateriumAuthBootV1776 .cab-wrap{width:100%}#cateriumAuthBootV1776 .cab-brand{margin-bottom:42px}#cateriumAuthBootV1776 .cab-mark{width:50px;height:50px;flex-basis:50px}#cateriumAuthBootV1776 .cab-word{font-size:29px}#cateriumAuthBootV1776 h1{font-size:clamp(38px,12vw,52px)}#cateriumAuthBootV1776 .cab-note{display:none}}
|
||
`;document.head.appendChild(style);
|
||
const el=document.createElement('div');el.id='cateriumAuthBootV1776';el.innerHTML='<div class="cab-wrap"><div class="cab-brand"><img class="cab-mark" src="caterium-mark-light.svg" alt=""><span class="cab-word">Caterium</span></div><h1>Открываю Caterium…</h1><p role="status">Проверяю сохранённый вход</p></div><div class="cab-note">Caterium · вкус в деталях</div>';document.documentElement.appendChild(el);
|
||
}
|
||
function loadLoginSignatureEarly(){if(window.CateriumLoginSignatureV1776||document.getElementById('cateriumLoginSignatureV1776Script'))return;const s=document.createElement('script');s.id='cateriumLoginSignatureV1776Script';s.src=`core/login-signature-v1776.js?v=${RELEASE}`;s.async=false;document.head.appendChild(s)}
|
||
installAuthBoot();loadLoginSignatureEarly();
|
||
// Boot cleanup is independent of the optional decorative script.
|
||
if(document.getElementById('cateriumAuthBootV1776')){
|
||
const bootObserver=new MutationObserver(()=>{
|
||
const boot=document.getElementById('cateriumAuthBootV1776');
|
||
if(!boot){bootObserver.disconnect();return}
|
||
if(document.getElementById('sunCloudAuthGateV3')||(window.SunAdminRBACV3&&!document.body?.classList.contains('sun-cloud-auth-required'))){boot.remove();bootObserver.disconnect()}
|
||
});
|
||
bootObserver.observe(document.documentElement,{childList:true,subtree:true,attributes:true,attributeFilter:['class']});
|
||
}
|
||
|
||
const critical=img=>img.closest('header,.brand,#sunCloudAuthGate,.sun-auth-gate')||img.id==='sunLoginLogo'||img.classList.contains('sun-live-catalog-logo');
|
||
const tune=img=>{if(!(img instanceof HTMLImageElement)||critical(img))return;if(!img.hasAttribute('loading'))img.loading='lazy';if(!img.hasAttribute('decoding'))img.decoding='async';if(!img.hasAttribute('fetchpriority'))img.setAttribute('fetchpriority','low');};
|
||
const scan=root=>{if(root instanceof HTMLImageElement)tune(root);root?.querySelectorAll?.('img').forEach(tune);};
|
||
const pendingImageRoots=new Set();let imageScanTimer=0;
|
||
function queueImageScan(root){if(!root||root.nodeType!==1)return;for(const existing of [...pendingImageRoots]){if(existing===root||existing.contains?.(root))return;if(root.contains?.(existing))pendingImageRoots.delete(existing)}pendingImageRoots.add(root);if(imageScanTimer)return;imageScanTimer=setTimeout(()=>{imageScanTimer=0;const roots=[...pendingImageRoots];pendingImageRoots.clear();roots.forEach(scan)},80);}
|
||
|
||
const MAX_FILE=15*1024*1024,TARGET=2*1024*1024,MAX_SIDE=2048;
|
||
const guardedInputs=new WeakSet();
|
||
const toast=(text,type='info',ms=4500)=>{try{return window.SunEnterprise?.toast?.(text,type,ms)}catch(_){}};
|
||
const jpegName=name=>String(name||'photo').replace(/\.[^.]+$/,'')+'.jpg';
|
||
const canvasBlob=(canvas,type,quality)=>new Promise((resolve,reject)=>canvas.toBlob(b=>b?resolve(b):reject(new Error('Не удалось сжать изображение.')),type,quality));
|
||
async function decodeImage(file){if('createImageBitmap' in window){const bitmap=await createImageBitmap(file);return {source:bitmap,width:bitmap.width,height:bitmap.height,close:()=>bitmap.close?.()};}const url=URL.createObjectURL(file);try{const img=new Image();await new Promise((resolve,reject)=>{img.onload=resolve;img.onerror=reject;img.src=url});return {source:img,width:img.naturalWidth,height:img.naturalHeight,close:()=>URL.revokeObjectURL(url)};}catch(e){URL.revokeObjectURL(url);throw e;}}
|
||
async function compressImage(file){if(!(file instanceof File)||!String(file.type||'').startsWith('image/')||/gif|svg/i.test(file.type||''))return file;let decoded;try{decoded=await decodeImage(file);}catch(_){return file;}try{const {width,height,source}=decoded;if(!width||!height)return file;if(file.size<=TARGET&&Math.max(width,height)<=MAX_SIDE)return file;let scale=Math.min(1,MAX_SIDE/Math.max(width,height)),quality=.84,best=null;for(let pass=0;pass<5;pass++){const w=Math.max(1,Math.round(width*scale)),h=Math.max(1,Math.round(height*scale));const canvas=document.createElement('canvas');canvas.width=w;canvas.height=h;const ctx=canvas.getContext('2d',{alpha:false});if(!ctx)return file;ctx.fillStyle='#fff';ctx.fillRect(0,0,w,h);ctx.drawImage(source,0,0,w,h);const blob=await canvasBlob(canvas,'image/jpeg',quality);if(!best||blob.size<best.size)best=blob;if(blob.size<=TARGET)break;quality=Math.max(.60,quality-.08);scale*=.86;}if(!best||best.size>=file.size)return file;return new File([best],jpegName(file.name),{type:'image/jpeg',lastModified:file.lastModified||Date.now()});}finally{decoded.close?.();}}
|
||
async function prepareAttachment(file){const prepared=await compressImage(file);if(prepared.size>MAX_FILE)throw new Error(`Файл «${file.name}» больше 15 МБ даже после оптимизации.`);return {file:prepared,originalSize:file.size,savedBytes:Math.max(0,file.size-prepared.size)};}
|
||
async function guardChatFiles(input){const raw=[...(input.files||[])];if(!raw.length)return;const prepared=[];let saved=0;for(const f of raw){try{const p=await prepareAttachment(f);prepared.push(p.file);saved+=p.savedBytes;}catch(e){toast(e.message||String(e),'warn',6000);}}if(!prepared.length){input.value='';return;}const dt=new DataTransfer();prepared.forEach(f=>dt.items.add(f));input.files=dt.files;guardedInputs.add(input);input.dispatchEvent(new Event('change',{bubbles:true}));guardedInputs.delete(input);if(saved>256*1024)toast(`Фото оптимизированы: сэкономлено ${(saved/1024/1024).toFixed(1)} МБ.`,'success',4200);}
|
||
document.addEventListener('change',e=>{const input=e.target;if(!(input instanceof HTMLInputElement)||input.id!=='sunChatFilesV29'||guardedInputs.has(input)||typeof DataTransfer==='undefined')return;e.preventDefault();e.stopImmediatePropagation();guardChatFiles(input).catch(err=>{console.error('[Caterium photo compression]',err);guardedInputs.add(input);input.dispatchEvent(new Event('change',{bubbles:true}));guardedInputs.delete(input);});},true);
|
||
window.SunAttachmentGuard={VERSION,MAX_FILE,TARGET,MAX_SIDE,compressImage,prepareAttachment};
|
||
|
||
let memoryData=null,memoryAt=0,memoryPromise=null,memoryTimer=0;
|
||
const MEMORY_CACHE_MS=15000,MEMORY_REFRESH_MS=30000,MEMORY_TIMEOUT_MS=8000;
|
||
const esc=v=>window.SunSafe?.escapeHTML?window.SunSafe.escapeHTML(String(v??'')):String(v??'');
|
||
const developerVisible=()=>document.getElementById('sun-developer-console-v22')?.classList.contains('on');
|
||
async function loadMemory({force=false}={}){const now=Date.now();if(memoryPromise)return memoryPromise;if(!force&&memoryData&&now-memoryAt<MEMORY_CACHE_MS)return memoryData;const c=window.SunCloudV2?.getClient?.();if(!c)return null;memoryPromise=(async()=>{let timer=0;try{const timeout=new Promise((_,reject)=>{timer=setTimeout(()=>reject(new Error('Developer memory timeout')),MEMORY_TIMEOUT_MS)});const rpc=Promise.resolve(c.rpc('sun_dev_dashboard'));const r=await Promise.race([rpc,timeout]);if(r?.error)throw r.error;memoryData=r?.data||null;memoryAt=Date.now();return memoryData;}catch(error){console.warn('[Caterium developer memory]',error?.message||error);return memoryData}finally{clearTimeout(timer);memoryPromise=null}})();return memoryPromise;}
|
||
async function enhanceDeveloperMemory({force=false}={}){const view=document.getElementById('sun-developer-console-v22');if(!view?.classList.contains('on'))return null;const body=document.getElementById('sunDevBody');if(!body)return null;const version=document.getElementById('sunDevReleaseVersion');if(version)version.textContent=VERSION;const d=await loadMemory({force});if(!d||!developerVisible())return d;let box=document.getElementById('sunDevMemoryV1761');if(!box){box=document.createElement('div');box.id='sunDevMemoryV1761';box.className='sun-dev-grid';box.style.marginBottom='12px';body.prepend(box);}const html=`<div class="sun-dev-kpi"><small>Память сервера</small><b>${esc(d.server_size||d.database_size||'—')}</b></div><div class="sun-dev-kpi"><small>База PostgreSQL</small><b>${esc(d.database_size||'—')}</b></div><div class="sun-dev-kpi"><small>Файлы Storage</small><b>${esc(d.storage_size||'—')}</b></div><div class="sun-dev-kpi"><small>Объектов Storage</small><b>${Number(d.storage_objects||0)}</b></div>`;if(box.innerHTML!==html)box.innerHTML=html;return d;}
|
||
function scheduleMemoryRefresh(delay=80,force=false){setTimeout(()=>{if(developerVisible())enhanceDeveloperMemory({force}).catch(()=>{})},delay)}
|
||
function startMemoryTimer(){if(memoryTimer)return;memoryTimer=setInterval(()=>{if(!document.hidden&&developerVisible())enhanceDeveloperMemory({force:true}).catch(()=>{})},MEMORY_REFRESH_MS)}
|
||
|
||
const loadScript=(id,src,flag,async=true)=>{if(window[flag]||document.getElementById(id))return;const script=document.createElement('script');script.id=id;script.src=src;script.async=async;script.onerror=()=>console.error(`[Caterium] Не загрузился ${src}`);document.head.appendChild(script);};
|
||
function loadDataLayer(){loadScript('cateriumDataV1773Script',`core/data-layer-v1773.js?v=${RELEASE}`,'CateriumDataV1773',false)}
|
||
function loadServerAutomation(){loadScript('cateriumServerAutomationV1770Script',`core/server-automation-v1770.js?v=${RELEASE}`,'CateriumServerAutomationV1770')}
|
||
function loadHotfix(){loadScript('sunHotfixV1763Script',`core/hotfix-v1763.js?v=${RELEASE}`,'SunHotfixV1763')}
|
||
function loadOpsUX(){loadScript('sunOpsUXV1762Script',`core/ops-ux-v1762.js?v=${RELEASE}`,'SunOpsUXV1762')}
|
||
function loadUXFix(){loadScript('sunUXFixV1764Script',`core/ux-fixes-v1764.js?v=${RELEASE}`,'SunUXFixV1764')}
|
||
function loadDeveloperUX(){loadScript('sunDeveloperUXV1768Script',`core/developer-console-v1768.js?v=${RELEASE}`,'SunDeveloperUXV1768')}
|
||
function loadOfferWorkspace(){loadScript('sunOfferWorkspaceV1769Script',`core/offer-workspace-v1769.js?v=${RELEASE}`,'SunOfferWorkspaceV1769')}
|
||
function loadAuthSecurity(){loadScript('cateriumAuthSecurityV1774Script',`core/auth-security-v1774.js?v=${RELEASE}`,'CateriumAuthSecurityV1774',false)}
|
||
function loadOrderEnhancements(){loadScript('cateriumOrderEnhancementsV1775Script',`core/order-enhancements-v1775.js?v=${RELEASE}`,'__cateriumOrderEnhancementsV1775')}
|
||
function loadLoginSignature(){loadScript('cateriumLoginSignatureV1776Script',`core/login-signature-v1776.js?v=${RELEASE}`,'CateriumLoginSignatureV1776',false)}
|
||
const start=()=>{loadLoginSignature();loadDataLayer();loadServerAutomation();loadHotfix();loadOpsUX();loadUXFix();loadDeveloperUX();loadOfferWorkspace();loadAuthSecurity();loadOrderEnhancements();scan(document);startMemoryTimer();const mo=new MutationObserver(records=>{records.forEach(r=>r.addedNodes.forEach(n=>{if(n.nodeType===1)queueImageScan(n)}));});mo.observe(document.documentElement,{childList:true,subtree:true});document.addEventListener('click',e=>{if(e.target.closest('#sunDeveloperNavV22,[data-dev-tab],#sunDevRefresh'))scheduleMemoryRefresh(100,true)},true);window.addEventListener('sun:cloud-state-applied',()=>scheduleMemoryRefresh(180,true));document.addEventListener('visibilitychange',()=>{if(!document.hidden&&developerVisible())scheduleMemoryRefresh(50,false)});window.SunPerformance={VERSION,scanImages:()=>scan(document),refreshDeveloperMemory:(force=true)=>enhanceDeveloperMemory({force}),loadDataLayer,loadServerAutomation,loadHotfix,loadOpsUX,loadUXFix,loadDeveloperUX,loadOfferWorkspace,loadAuthSecurity,loadOrderEnhancements,loadLoginSignature,disconnect:()=>{mo.disconnect();if(memoryTimer){clearInterval(memoryTimer);memoryTimer=0}if(imageScanTimer){clearTimeout(imageScanTimer);imageScanTimer=0}pendingImageRoots.clear();}};};
|
||
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',start,{once:true});else start();
|
||
})(); |