Fix Safari login request bodies and raw storage uploads
This commit is contained in:
parent
4d4cca2e32
commit
09bfb0fed8
2
.github/workflows/qa.yml
vendored
2
.github/workflows/qa.yml
vendored
@ -16,5 +16,5 @@ jobs:
|
|||||||
- run: npm ci
|
- run: npm ci
|
||||||
- run: npm audit --audit-level=high
|
- run: npm audit --audit-level=high
|
||||||
- run: npm run check:deploy
|
- run: npm run check:deploy
|
||||||
- run: npx playwright install --with-deps chromium
|
- run: npx playwright install --with-deps chromium webkit
|
||||||
- run: npm run test:e2e
|
- run: npm run test:e2e
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
"serverReady": true,
|
"serverReady": true,
|
||||||
"workspaceAutoDiscovery": true,
|
"workspaceAutoDiscovery": true,
|
||||||
"invitesTemporarilyDisabled": false,
|
"invitesTemporarilyDisabled": false,
|
||||||
"pwaCache": "v101-20260918-import-paid",
|
"pwaCache": "v102-20260918-safari-login",
|
||||||
"fullOfferDescriptions": true,
|
"fullOfferDescriptions": true,
|
||||||
"dynamicOfferRows": true,
|
"dynamicOfferRows": true,
|
||||||
"pdfOfferDescriptionFix": true,
|
"pdfOfferDescriptionFix": true,
|
||||||
|
|||||||
@ -30,7 +30,7 @@ const ALLOWED_ORIGINS = [
|
|||||||
|
|
||||||
const FORWARD_REQUEST_HEADERS = [
|
const FORWARD_REQUEST_HEADERS = [
|
||||||
'authorization', 'apikey', 'content-type', 'prefer', 'range',
|
'authorization', 'apikey', 'content-type', 'prefer', 'range',
|
||||||
'x-client-info', 'x-supabase-api-version', 'accept-profile', 'content-profile', 'x-upsert',
|
'x-client-info', 'x-supabase-api-version', 'accept-profile', 'content-profile', 'x-upsert', 'cache-control',
|
||||||
];
|
];
|
||||||
|
|
||||||
const STRIP_RESPONSE_HEADERS = [
|
const STRIP_RESPONSE_HEADERS = [
|
||||||
|
|||||||
@ -2710,7 +2710,7 @@ window.SUN_LEGACY_CATALOG_V175=[];
|
|||||||
const hash = await sha256(blob);
|
const hash = await sha256(blob);
|
||||||
const ext = extensionFor(blob.type);
|
const ext = extensionFor(blob.type);
|
||||||
const path = `${workspace.id}/media/${hash.slice(0,2)}/${hash}.${ext}`;
|
const path = `${workspace.id}/media/${hash.slice(0,2)}/${hash}.${ext}`;
|
||||||
const {error} = await client.storage.from(BUCKET).upload(path, blob, {upsert:false,contentType:blob.type || 'application/octet-stream',cacheControl:'31536000'});
|
const {error} = await client.storage.from(BUCKET).upload(path, await blob.arrayBuffer(), {upsert:false,contentType:blob.type || 'application/octet-stream',cacheControl:'31536000'});
|
||||||
if (error && !/exist|duplicate|409/i.test(`${error.message || ''} ${error.statusCode || ''}`)) throw error;
|
if (error && !/exist|duplicate|409/i.test(`${error.message || ''} ${error.statusCode || ''}`)) throw error;
|
||||||
await idbPut('media', {path, blob, cachedAt:new Date().toISOString()});
|
await idbPut('media', {path, blob, cachedAt:new Date().toISOString()});
|
||||||
return MEDIA_PREFIX + path;
|
return MEDIA_PREFIX + path;
|
||||||
@ -5764,7 +5764,7 @@ window.SUN_LEGACY_CATALOG_V175=[];
|
|||||||
async function uploadPending(){
|
async function uploadPending(){
|
||||||
const c=client(),ws=workspace();if(!c||!ws||!activeThread)return [];
|
const c=client(),ws=workspace();if(!c||!ws||!activeThread)return [];
|
||||||
const result=[];
|
const result=[];
|
||||||
for(const f of pendingFiles){const path=`${ws.id}/${activeThread.thread_id}/${uid()}-${safeName(f.name)}`;const up=await c.storage.from('sun-chat').upload(path,f,{contentType:f.type||'application/octet-stream',upsert:false});if(up.error)throw up.error;result.push({path,name:f.name,type:f.type||'application/octet-stream',size:f.size});}
|
for(const f of pendingFiles){const path=`${ws.id}/${activeThread.thread_id}/${uid()}-${safeName(f.name)}`;const up=await c.storage.from('sun-chat').upload(path,await f.arrayBuffer(),{contentType:f.type||'application/octet-stream',upsert:false});if(up.error)throw up.error;result.push({path,name:f.name,type:f.type||'application/octet-stream',size:f.size});}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,10 @@
|
|||||||
let directUntil=0;
|
let directUntil=0;
|
||||||
return async function(input,init={}){
|
return async function(input,init={}){
|
||||||
const original=new Request(input,init),url=new URL(original.url),isBackend=url.origin===origin;
|
const original=new Request(input,init),url=new URL(original.url),isBackend=url.origin===origin;
|
||||||
|
// A Request used as RequestInit exposes its ReadableStream body. Safari
|
||||||
|
// cannot upload that stream; buffer once and preserve bytes on fallback.
|
||||||
|
const body=original.method==='GET'||original.method==='HEAD'?undefined:await original.clone().arrayBuffer();
|
||||||
|
const requestInit={method:original.method,headers:original.headers,body,credentials:original.credentials,mode:original.mode,cache:original.cache,redirect:original.redirect,referrer:original.referrer,referrerPolicy:original.referrerPolicy,integrity:original.integrity,keepalive:original.keepalive};
|
||||||
const read=original.method==='GET'||original.method==='HEAD'||(original.method==='POST'&&url.pathname.startsWith('/rest/v1/rpc/')&&READ_RPCS.has(url.pathname.slice('/rest/v1/rpc/'.length)));
|
const read=original.method==='GET'||original.method==='HEAD'||(original.method==='POST'&&url.pathname.startsWith('/rest/v1/rpc/')&&READ_RPCS.has(url.pathname.slice('/rest/v1/rpc/'.length)));
|
||||||
const passwordLogin=original.method==='POST'&&url.pathname==='/auth/v1/token'&&url.searchParams.get('grant_type')==='password';
|
const passwordLogin=original.method==='POST'&&url.pathname==='/auth/v1/token'&&url.searchParams.get('grant_type')==='password';
|
||||||
const safeFallback=isBackend&&(read||passwordLogin);
|
const safeFallback=isBackend&&(read||passwordLogin);
|
||||||
@ -18,7 +22,7 @@
|
|||||||
const timer=setTimeout(()=>controller.abort(new DOMException('Сервер не ответил вовремя. Проверьте соединение и повторите загрузку.','TimeoutError')),limit);
|
const timer=setTimeout(()=>controller.abort(new DOMException('Сервер не ответил вовремя. Проверьте соединение и повторите загрузку.','TimeoutError')),limit);
|
||||||
try{
|
try{
|
||||||
if(controller.signal.aborted)throw controller.signal.reason;
|
if(controller.signal.aborted)throw controller.signal.reason;
|
||||||
const response=await fetch(new Request(target,original.clone()),{signal:controller.signal});
|
const response=await fetch(new Request(target,requestInit),{signal:controller.signal});
|
||||||
if(expectJson&&response.ok&&response.status!==204){
|
if(expectJson&&response.ok&&response.status!==204){
|
||||||
const text=await response.clone().text();
|
const text=await response.clone().text();
|
||||||
try{if(!text.trim()||!response.headers.get('content-type')?.includes('json'))throw new Error();JSON.parse(text)}
|
try{if(!text.trim()||!response.headers.get('content-type')?.includes('json'))throw new Error();JSON.parse(text)}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
(()=>{
|
(()=>{
|
||||||
'use strict';
|
'use strict';
|
||||||
const VERSION='17.7.3';
|
const VERSION='17.7.3';
|
||||||
const RELEASE='20260918-import-paid';
|
const RELEASE='20260918-safari-login';
|
||||||
|
|
||||||
const hasStoredSession=()=>{try{return Object.keys(localStorage).some(k=>/^sb-.*-auth-token$/i.test(k)&&String(localStorage.getItem(k)||'').length>20)}catch(_){return false}};
|
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(){
|
function installAuthBoot(){
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -1,5 +1,5 @@
|
|||||||
const CACHE='sun-catering-pwa-v101-20260918-import-paid';
|
const CACHE='sun-catering-pwa-v102-20260918-safari-login';
|
||||||
const VERSION='20260918-import-paid';
|
const VERSION='20260918-safari-login';
|
||||||
const CORE=[
|
const CORE=[
|
||||||
'./','./index.html',`./core/mobile-order.js?v=${VERSION}`,`./core/proposal-layout.js?v=${VERSION}`,'./fonts/Manrope.ttf','./fonts/PlayfairDisplay.ttf','./fonts/PlayfairDisplay-Italic.ttf',`./core/trial-demo.js?v=${VERSION}`,`./core/cloud-transport.js?v=${VERSION}`,`./core/banquet-menu.js?v=${VERSION}`,`./core/access-policy.js?v=${VERSION}`,`./core/import-archive.js?v=${VERSION}`,`./core/company-branding.js?v=${VERSION}`,`./core/signature-offer-pdf-v18.js?v=${VERSION}`,`./core/brand-theme.js?v=${VERSION}`,
|
'./','./index.html',`./core/mobile-order.js?v=${VERSION}`,`./core/proposal-layout.js?v=${VERSION}`,'./fonts/Manrope.ttf','./fonts/PlayfairDisplay.ttf','./fonts/PlayfairDisplay-Italic.ttf',`./core/trial-demo.js?v=${VERSION}`,`./core/cloud-transport.js?v=${VERSION}`,`./core/banquet-menu.js?v=${VERSION}`,`./core/access-policy.js?v=${VERSION}`,`./core/import-archive.js?v=${VERSION}`,`./core/company-branding.js?v=${VERSION}`,`./core/signature-offer-pdf-v18.js?v=${VERSION}`,`./core/brand-theme.js?v=${VERSION}`,
|
||||||
`./core/sun-safe.js?v=${VERSION}`,`./core/performance.js?v=${VERSION}`,`./core/account-center-v1780.js?v=${VERSION}`,`./core/login-signature-v1776.js?v=${VERSION}`,`./core/login-signature-v1776.css?v=${VERSION}`,`./core/data-layer-v1773.js?v=${VERSION}`,`./core/server-automation-v1770.js?v=${VERSION}`,`./core/hotfix-v1763.js?v=${VERSION}`,`./core/ops-ux-v1762.js?v=${VERSION}`,`./core/ux-fixes-v1764.js?v=${VERSION}`,`./core/pdf-engine.js?v=${VERSION}`,`./core/classic-offer-pdf-v1767.js?v=${VERSION}`,`./core/developer-console-v1768.js?v=${VERSION}`,`./core/offer-workspace-v1769.js?v=${VERSION}`,`./core/auth-security-v1774.js?v=${VERSION}`,`./core/order-enhancements-v1775.js?v=${VERSION}`,`./legacy/bootstrap.js?v=${VERSION}`,`./app-runtime.js?v=${VERSION}`,
|
`./core/sun-safe.js?v=${VERSION}`,`./core/performance.js?v=${VERSION}`,`./core/account-center-v1780.js?v=${VERSION}`,`./core/login-signature-v1776.js?v=${VERSION}`,`./core/login-signature-v1776.css?v=${VERSION}`,`./core/data-layer-v1773.js?v=${VERSION}`,`./core/server-automation-v1770.js?v=${VERSION}`,`./core/hotfix-v1763.js?v=${VERSION}`,`./core/ops-ux-v1762.js?v=${VERSION}`,`./core/ux-fixes-v1764.js?v=${VERSION}`,`./core/pdf-engine.js?v=${VERSION}`,`./core/classic-offer-pdf-v1767.js?v=${VERSION}`,`./core/developer-console-v1768.js?v=${VERSION}`,`./core/offer-workspace-v1769.js?v=${VERSION}`,`./core/auth-security-v1774.js?v=${VERSION}`,`./core/order-enhancements-v1775.js?v=${VERSION}`,`./legacy/bootstrap.js?v=${VERSION}`,`./app-runtime.js?v=${VERSION}`,
|
||||||
|
|||||||
@ -1,6 +1,25 @@
|
|||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import {test,expect} from '@playwright/test';
|
import {test,expect} from '@playwright/test';
|
||||||
|
|
||||||
|
test('Safari without streaming uploads can send login and binary bodies through fallback',async({page})=>{
|
||||||
|
await page.route('**/index.html',r=>r.fulfill({contentType:'text/html',body:'<html><body></body></html>'}));
|
||||||
|
await page.goto('/index.html');await page.addScriptTag({url:'/core/cloud-transport.js'});
|
||||||
|
const result=await page.evaluate(async()=>{
|
||||||
|
const NativeRequest=window.Request;
|
||||||
|
window.Request=new Proxy(NativeRequest,{construct(Target,args){if(args[1]?.body instanceof ReadableStream)throw new TypeError('ReadableStream uploading is not supported');return new Target(...args)}});
|
||||||
|
const calls=[];
|
||||||
|
window.fetch=async request=>{calls.push({url:request.url,body:[...new Uint8Array(await request.arrayBuffer())],type:request.headers.get('content-type')});return request.url.includes('proxy.example')?new Response('',{status:503}):new Response('{}',{headers:{'content-type':'application/json'}})};
|
||||||
|
const send=CateriumCloudTransport.create({upstream:'https://backend.example',proxy:'https://proxy.example',cooldown:0});
|
||||||
|
const body=JSON.stringify({email:'test@example.invalid',password:'test-password'});
|
||||||
|
await send('https://backend.example/auth/v1/token?grant_type=password',{method:'POST',headers:{'content-type':'application/json'},body});
|
||||||
|
await send('https://backend.example/storage/v1/object/test/image',{method:'POST',headers:{'content-type':'image/png'},body:new Uint8Array([0,255,137,80]).buffer});
|
||||||
|
return {calls,expected:[...new TextEncoder().encode(body)]};
|
||||||
|
});
|
||||||
|
expect(result.calls).toHaveLength(3);
|
||||||
|
expect(result.calls[0].body).toEqual(result.expected);expect(result.calls[1].body).toEqual(result.expected);
|
||||||
|
expect(result.calls[2].body).toEqual([0,255,137,80]);expect(result.calls[2].type).toBe('image/png');
|
||||||
|
});
|
||||||
|
|
||||||
async function delayedSessionApp(page,signedIn=true){
|
async function delayedSessionApp(page,signedIn=true){
|
||||||
await page.route('https://**',r=>r.abort());
|
await page.route('https://**',r=>r.abort());
|
||||||
await page.addInitScript(signed=>{
|
await page.addInitScript(signed=>{
|
||||||
@ -72,10 +91,20 @@ async function syncHarness(page){
|
|||||||
await page.route('**/index.html',r=>r.fulfill({contentType:'text/html',body:'<html><body></body></html>'}));await page.goto('/index.html');
|
await page.route('**/index.html',r=>r.fulfill({contentType:'text/html',body:'<html><body></body></html>'}));await page.goto('/index.html');
|
||||||
await page.addScriptTag({url:'/core/sun-safe.js'});await page.addScriptTag({url:'/core/cloud-transport.js'});
|
await page.addScriptTag({url:'/core/sun-safe.js'});await page.addScriptTag({url:'/core/cloud-transport.js'});
|
||||||
const runtime=fs.readFileSync('public/app-runtime.js','utf8');let source=runtime.slice(runtime.indexOf('/* ===== MODULE: cloud-sync-v2.js'),runtime.indexOf('/* ===== MODULE: admin-rbac-v3.js'));
|
const runtime=fs.readFileSync('public/app-runtime.js','utf8');let source=runtime.slice(runtime.indexOf('/* ===== MODULE: cloud-sync-v2.js'),runtime.indexOf('/* ===== MODULE: admin-rbac-v3.js'));
|
||||||
source=source.replace('async function boot(){','async function boot(){return;').replace('window.SunCloudV2={',`window.SunCloudV2={testInit:c=>{client=c;session={user:{id:'test-user'}};workspace={id:'company',role:'admin'};config.migrated.company=true;config.tenantStorageReady=true;config.localWorkspaceId='company';config.workspaceId='company'},testBaseline:setBaseline,testLeave:()=>{session=null;workspace=null},`);
|
source=source.replace('async function boot(){','async function boot(){return;').replace('window.SunCloudV2={',`window.SunCloudV2={testUploadDataUrl:uploadDataUrl,testInit:c=>{client=c;session={user:{id:'test-user'}};workspace={id:'company',role:'admin'};config.migrated.company=true;config.tenantStorageReady=true;config.localWorkspaceId='company';config.workspaceId='company'},testBaseline:setBaseline,testLeave:()=>{session=null;workspace=null},`);
|
||||||
await page.addScriptTag({content:'var orders=[],boxes=[];'+source});
|
await page.addScriptTag({content:'var orders=[],boxes=[];'+source});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test('cloud photos use raw bytes rather than multipart consumed by the PHP proxy',async({page})=>{
|
||||||
|
await syncHarness(page);
|
||||||
|
const result=await page.evaluate(async()=>{
|
||||||
|
let uploaded;
|
||||||
|
SunCloudV2.testInit({storage:{from:()=>({upload:async(path,body,options)=>{uploaded={path,raw:body instanceof ArrayBuffer,bytes:[...new Uint8Array(body)],options};return {error:null}}})}});
|
||||||
|
await SunCloudV2.testUploadDataUrl('data:image/png;base64,AP+JUA==');return uploaded;
|
||||||
|
});
|
||||||
|
expect(result.raw).toBe(true);expect(result.bytes).toEqual([0,255,137,80]);expect(result.options.contentType).toBe('image/png');expect(result.path).toMatch(/^company\/media\//);
|
||||||
|
});
|
||||||
|
|
||||||
test('an uncertain save is recovered by reading its committed revision without a duplicate write',async({page})=>{
|
test('an uncertain save is recovered by reading its committed revision without a duplicate write',async({page})=>{
|
||||||
await syncHarness(page);await page.clock.install();
|
await syncHarness(page);await page.clock.install();
|
||||||
await page.evaluate(async()=>{
|
await page.evaluate(async()=>{
|
||||||
|
|||||||
@ -7,6 +7,7 @@ export default defineConfig({
|
|||||||
use:{baseURL:'http://127.0.0.1:4173'},
|
use:{baseURL:'http://127.0.0.1:4173'},
|
||||||
webServer:{command:'npx http-server public -p 4173 -c-1',cwd:fileURLToPath(new URL('../',import.meta.url)),port:4173,reuseExistingServer:true},
|
webServer:{command:'npx http-server public -p 4173 -c-1',cwd:fileURLToPath(new URL('../',import.meta.url)),port:4173,reuseExistingServer:true},
|
||||||
projects:[
|
projects:[
|
||||||
|
{name:'iphone-webkit',testMatch:['login-recovery.spec.mjs','account-access.spec.mjs'],use:{...devices['iPhone 13'],serviceWorkers:'block'}},
|
||||||
{name:'desktop',use:{...devices['Desktop Chrome']}},
|
{name:'desktop',use:{...devices['Desktop Chrome']}},
|
||||||
{name:'mobile-390',use:{viewport:{width:390,height:844},isMobile:true,hasTouch:true}}
|
{name:'mobile-390',use:{viewport:{width:390,height:844},isMobile:true,hasTouch:true}}
|
||||||
]
|
]
|
||||||
|
|||||||
@ -14,8 +14,8 @@ check(!index.includes('offer-gallery-data.js'),'blocking Base64 gallery absent')
|
|||||||
check((runtime.match(/\/Type \/Catalog/g)||[]).length===0,'runtime contains no PDF binary writer');
|
check((runtime.match(/\/Type \/Catalog/g)||[]).length===0,'runtime contains no PDF binary writer');
|
||||||
check(read('core/pdf-engine.js').includes('595.28')&&read('core/pdf-engine.js').includes('841.89'),'PDF engine uses A4 MediaBox');
|
check(read('core/pdf-engine.js').includes('595.28')&&read('core/pdf-engine.js').includes('841.89'),'PDF engine uses A4 MediaBox');
|
||||||
check([...index.matchAll(/@page\{([^}]*)\}/g)].every(m=>/size:A4/i.test(m[1])),'compact @page rules use A4');
|
check([...index.matchAll(/@page\{([^}]*)\}/g)].every(m=>/size:A4/i.test(m[1])),'compact @page rules use A4');
|
||||||
check(sw.includes('v101-20260918-import-paid')&&sw.includes('data-layer-v1773.js')&&sw.includes('server-automation-v1770.js')&&sw.includes('offer-workspace-v1769.js'),'service worker cache is v17.7.3');
|
check(sw.includes('v102-20260918-safari-login')&&sw.includes('data-layer-v1773.js')&&sw.includes('server-automation-v1770.js')&&sw.includes('offer-workspace-v1769.js'),'service worker cache is v17.7.3');
|
||||||
check(index.includes('20260918-import-paid')&&index.includes('classic-offer-pdf-v1767.js')&&!index.includes('20260907-v17-6-0-stability-security'),'index cache-busting points to v17.7.3');
|
check(index.includes('20260918-safari-login')&&index.includes('classic-offer-pdf-v1767.js')&&!index.includes('20260907-v17-6-0-stability-security'),'index cache-busting points to v17.7.3');
|
||||||
check(performance.includes('SunAttachmentGuard')&&performance.includes('TARGET=2*1024*1024'),'chat photo auto-compression is versioned');
|
check(performance.includes('SunAttachmentGuard')&&performance.includes('TARGET=2*1024*1024'),'chat photo auto-compression is versioned');
|
||||||
check(performance.includes("rpc('sun_dev_dashboard')")&&performance.includes('server_size')&&performance.includes('storage_size'),'Developer Console server/storage counters are versioned');
|
check(performance.includes("rpc('sun_dev_dashboard')")&&performance.includes('server_size')&&performance.includes('storage_size'),'Developer Console server/storage counters are versioned');
|
||||||
check(performance.includes('MEMORY_REFRESH_MS=30000')&&performance.includes('MEMORY_TIMEOUT_MS=8000')&&performance.includes('memoryPromise'),'Developer Console memory refresh is bounded');
|
check(performance.includes('MEMORY_REFRESH_MS=30000')&&performance.includes('MEMORY_TIMEOUT_MS=8000')&&performance.includes('memoryPromise'),'Developer Console memory refresh is bounded');
|
||||||
@ -39,7 +39,7 @@ check(!/sb_secret_[A-Za-z0-9_-]{20,}|service_role\s*[:=]\s*["'][A-Za-z0-9._-]{30
|
|||||||
check(lock.version===pkg.version&&lock.packages?.['']?.version===pkg.version,'package.json and package-lock.json versions match');
|
check(lock.version===pkg.version&&lock.packages?.['']?.version===pkg.version,'package.json and package-lock.json versions match');
|
||||||
check(releaseManifest.version===`v${pkg.version}`,'release manifest version matches package.json');
|
check(releaseManifest.version===`v${pkg.version}`,'release manifest version matches package.json');
|
||||||
check(releaseManifest.channel==='production','release manifest channel is production');
|
check(releaseManifest.channel==='production','release manifest channel is production');
|
||||||
check(String(releaseManifest.pwaCache||'').includes('v101-20260918-import-paid'),'release manifest points to current PWA cache');
|
check(String(releaseManifest.pwaCache||'').includes('v102-20260918-safari-login'),'release manifest points to current PWA cache');
|
||||||
check(['17.6.2','17.6.3','17.6.4','17.6.5','17.6.6','17.6.7','17.6.8','17.6.9','17.7.0','17.7.1','17.7.2','17.7.3'].every(v=>fs.existsSync(path.join(root,`docs/releases/V${v}-CHANGES.txt`))),'release notes exist through v17.7.3');
|
check(['17.6.2','17.6.3','17.6.4','17.6.5','17.6.6','17.6.7','17.6.8','17.6.9','17.7.0','17.7.1','17.7.2','17.7.3'].every(v=>fs.existsSync(path.join(root,`docs/releases/V${v}-CHANGES.txt`))),'release notes exist through v17.7.3');
|
||||||
check(runtime.includes('CLOUD_RPC_TIMEOUT_MS=45000')&&runtime.includes('CLOUD_CONFLICT_MAX_RETRIES=4')&&runtime.includes('retryCount'),'cloud sync has timeout and capped exponential conflict retries');
|
check(runtime.includes('CLOUD_RPC_TIMEOUT_MS=45000')&&runtime.includes('CLOUD_CONFLICT_MAX_RETRIES=4')&&runtime.includes('retryCount'),'cloud sync has timeout and capped exponential conflict retries');
|
||||||
check(runtime.includes("const VERSION = '17.7.3'")&&runtime.includes('ERROR_DEDUPE_MS=5*60*1000')&&runtime.includes('mirrorBusy=false')&&runtime.includes('backupBusy=false'),'stability logger uses current version, dedupe and single-flight guards');
|
check(runtime.includes("const VERSION = '17.7.3'")&&runtime.includes('ERROR_DEDUPE_MS=5*60*1000')&&runtime.includes('mirrorBusy=false')&&runtime.includes('backupBusy=false'),'stability logger uses current version, dedupe and single-flight guards');
|
||||||
@ -65,8 +65,8 @@ check(offerWorkspace.includes('PDF и предпросмотр')&&offerWorkspace
|
|||||||
check(offerWorkspace.includes('SunClassicOfferPDFV1767')&&offerWorkspace.includes('finalGallery=galleryFor'),'custom gallery is injected into PDF renderer');
|
check(offerWorkspace.includes('SunClassicOfferPDFV1767')&&offerWorkspace.includes('finalGallery=galleryFor'),'custom gallery is injected into PDF renderer');
|
||||||
check(releaseManifest.offerWorkspaceTabs===true&&releaseManifest.offerTemplatesSeparateTab===true&&releaseManifest.offerTwoCustomGalleryPhotos===true,'release manifest records offer workspace changes');
|
check(releaseManifest.offerWorkspaceTabs===true&&releaseManifest.offerTemplatesSeparateTab===true&&releaseManifest.offerTwoCustomGalleryPhotos===true,'release manifest records offer workspace changes');
|
||||||
check(pkg.version==='17.7.3','package version is v17.7.3');
|
check(pkg.version==='17.7.3','package version is v17.7.3');
|
||||||
check(index.includes('20260918-import-paid'),'index cache bust is v17.7.3');
|
check(index.includes('20260918-safari-login'),'index cache bust is v17.7.3');
|
||||||
check(sw.includes('v101-20260918-import-paid')&&sw.includes('data-layer-v1773.js')&&sw.includes('server-automation-v1770.js'),'PWA caches v17.7.3 client foundation modules');
|
check(sw.includes('v102-20260918-safari-login')&&sw.includes('data-layer-v1773.js')&&sw.includes('server-automation-v1770.js'),'PWA caches v17.7.3 client foundation modules');
|
||||||
check(fs.existsSync(path.join(root,'public/core/data-layer-v1773.js'))&&fs.existsSync(path.join(root,'public/core/server-automation-v1770.js')),'data layer and server automation modules exist');
|
check(fs.existsSync(path.join(root,'public/core/data-layer-v1773.js'))&&fs.existsSync(path.join(root,'public/core/server-automation-v1770.js')),'data layer and server automation modules exist');
|
||||||
check(ux.includes('CateriumServerAutomationV1770?.enabled'),'cloud browser auto completion is disabled when server automation is active');
|
check(ux.includes('CateriumServerAutomationV1770?.enabled'),'cloud browser auto completion is disabled when server automation is active');
|
||||||
check(runtime.includes("const VERSION = '17.7.3'")&&runtime.includes("v17.7.3 Clients Server Read"),'stability logger reports v17.7.3');
|
check(runtime.includes("const VERSION = '17.7.3'")&&runtime.includes("v17.7.3 Clients Server Read"),'stability logger reports v17.7.3');
|
||||||
|
|||||||
@ -28,8 +28,8 @@ if(current!==113)fail(`current catalog photo count ${current}, expected 113`);el
|
|||||||
if(legacyCount!==60)fail(`legacy catalog photo count ${legacyCount}, expected 60`);else ok('60 legacy catalog photos');
|
if(legacyCount!==60)fail(`legacy catalog photo count ${legacyCount}, expected 60`);else ok('60 legacy catalog photos');
|
||||||
const gallery=fs.readdirSync(path.join(pub,'offer-gallery')).filter(x=>/\.jpg$/i.test(x));
|
const gallery=fs.readdirSync(path.join(pub,'offer-gallery')).filter(x=>/\.jpg$/i.test(x));
|
||||||
if(gallery.length!==2)fail(`offer gallery contains ${gallery.length} jpg files, expected 2`);else ok('offer gallery trimmed');
|
if(gallery.length!==2)fail(`offer gallery contains ${gallery.length} jpg files, expected 2`);else ok('offer gallery trimmed');
|
||||||
if(!sw.includes('20260918-import-paid')||!sw.includes('login-signature-v1776.js')||!sw.includes('data-layer-v1773.js')||!sw.includes('server-automation-v1770.js')||!sw.includes('offer-workspace-v1769.js')||sw.includes('offer-gallery-data.js'))fail('service worker cache is stale');else ok('PWA cache updated for login refresh');
|
if(!sw.includes('20260918-safari-login')||!sw.includes('login-signature-v1776.js')||!sw.includes('data-layer-v1773.js')||!sw.includes('server-automation-v1770.js')||!sw.includes('offer-workspace-v1769.js')||sw.includes('offer-gallery-data.js'))fail('service worker cache is stale');else ok('PWA cache updated for login refresh');
|
||||||
if(html.includes('20260907-v17-6-0-stability-security')||html.includes('20260909-v17-7-3-clients-server-read')||!html.includes('20260918-import-paid')||!html.includes('classic-offer-pdf-v1767.js'))fail('index still serves stale core asset version');else ok('index cache-busting is current');
|
if(html.includes('20260907-v17-6-0-stability-security')||html.includes('20260909-v17-7-3-clients-server-read')||!html.includes('20260918-safari-login')||!html.includes('classic-offer-pdf-v1767.js'))fail('index still serves stale core asset version');else ok('index cache-busting is current');
|
||||||
if(!performance.includes('SunAttachmentGuard')||!performance.includes('MAX_SIDE=2048'))fail('chat photo compression guard missing');else ok('chat photo compression guard present');
|
if(!performance.includes('SunAttachmentGuard')||!performance.includes('MAX_SIDE=2048'))fail('chat photo compression guard missing');else ok('chat photo compression guard present');
|
||||||
if(!performance.includes("rpc('sun_dev_dashboard')")||!performance.includes('storage_size')||!performance.includes('server_size'))fail('Developer Console memory counters missing');else ok('Developer Console memory counters present');
|
if(!performance.includes("rpc('sun_dev_dashboard')")||!performance.includes('storage_size')||!performance.includes('server_size'))fail('Developer Console memory counters missing');else ok('Developer Console memory counters present');
|
||||||
if(performance.includes('records.forEach(r=>r.addedNodes.forEach(n=>{if(n.nodeType===1)scan(n)}));enhanceDeveloperMemory()'))fail('Developer Console memory refresh is still coupled to MutationObserver');else ok('Developer Console memory refresh loop removed');
|
if(performance.includes('records.forEach(r=>r.addedNodes.forEach(n=>{if(n.nodeType===1)scan(n)}));enhanceDeveloperMemory()'))fail('Developer Console memory refresh is still coupled to MutationObserver');else ok('Developer Console memory refresh loop removed');
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user