@xiboplayer/pwa 0.3.1 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/cache-proxy-CrlayfSe.js +2 -0
- package/dist/assets/cache-proxy-CrlayfSe.js.map +1 -0
- package/dist/assets/cms-api-Ce7EVg5h.js +2 -0
- package/dist/assets/cms-api-Ce7EVg5h.js.map +1 -0
- package/dist/assets/html2canvas.esm-CBrSDip1.js +23 -0
- package/dist/assets/html2canvas.esm-CBrSDip1.js.map +1 -0
- package/dist/assets/index-BF8qB-pu.js +2 -0
- package/dist/assets/index-BF8qB-pu.js.map +1 -0
- package/dist/assets/index-Baows0WY.js +2 -0
- package/dist/assets/index-Baows0WY.js.map +1 -0
- package/dist/assets/index-Be_IxwIZ.js +2 -0
- package/dist/assets/index-Be_IxwIZ.js.map +1 -0
- package/dist/assets/index-BhHwWvzx.js +2 -0
- package/dist/assets/index-BhHwWvzx.js.map +1 -0
- package/dist/assets/index-C77HSi9N.js +8 -0
- package/dist/assets/index-C77HSi9N.js.map +1 -0
- package/dist/assets/index-ChPoQ8Bt.js +607 -0
- package/dist/assets/index-ChPoQ8Bt.js.map +1 -0
- package/dist/assets/index-DPR3fBRV.js +2 -0
- package/dist/assets/index-DPR3fBRV.js.map +1 -0
- package/dist/assets/index-b1tfCACR.js +2 -0
- package/dist/assets/index-b1tfCACR.js.map +1 -0
- package/dist/assets/index-es8y3c70.js +2 -0
- package/dist/assets/index-es8y3c70.js.map +1 -0
- package/dist/assets/main-BUvkpHsV.js +44 -0
- package/dist/assets/main-BUvkpHsV.js.map +1 -0
- package/dist/assets/modulepreload-polyfill-B5Qt9EMX.js +2 -0
- package/dist/assets/modulepreload-polyfill-B5Qt9EMX.js.map +1 -0
- package/dist/assets/pdf-BnPRJEQ6.js +13 -0
- package/dist/assets/pdf-BnPRJEQ6.js.map +1 -0
- package/dist/assets/setup-B9GCkQRS.js +2 -0
- package/dist/assets/setup-B9GCkQRS.js.map +1 -0
- package/dist/assets/xmds-client-MaDHqpeL.js +16 -0
- package/dist/assets/xmds-client-MaDHqpeL.js.map +1 -0
- package/dist/index.html +130 -0
- package/dist/setup.html +371 -0
- package/dist/sw-pwa.js +2 -0
- package/dist/sw-pwa.js.map +1 -0
- package/dist/sw.test.js +271 -0
- package/package.json +9 -8
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{b as N,c as j}from"./cms-api-Ce7EVg5h.js";class I{constructor(){this.events=new Map}on(a,s){this.events.has(a)||this.events.set(a,[]),this.events.get(a).push(s)}once(a,s){const c=(...l)=>{s(...l),this.off(a,c)};this.on(a,c)}off(a,s){if(!this.events.has(a))return;const c=this.events.get(a),l=c.indexOf(s);l!==-1&&c.splice(l,1)}emit(a,...s){if(!this.events.has(a))return;const c=this.events.get(a).slice();for(const l of c)l(...s)}removeAllListeners(a){a?this.events.delete(a):this.events.clear()}}function K(E){return E&&E.__esModule&&Object.prototype.hasOwnProperty.call(E,"default")?E.default:E}var H={exports:{}};(function(E,a){(function(s){E.exports=s()})(function(s){var c=["0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"];function l(i,o){var t=i[0],e=i[1],n=i[2],r=i[3];t+=(e&n|~e&r)+o[0]-680876936|0,t=(t<<7|t>>>25)+e|0,r+=(t&e|~t&n)+o[1]-389564586|0,r=(r<<12|r>>>20)+t|0,n+=(r&t|~r&e)+o[2]+606105819|0,n=(n<<17|n>>>15)+r|0,e+=(n&r|~n&t)+o[3]-1044525330|0,e=(e<<22|e>>>10)+n|0,t+=(e&n|~e&r)+o[4]-176418897|0,t=(t<<7|t>>>25)+e|0,r+=(t&e|~t&n)+o[5]+1200080426|0,r=(r<<12|r>>>20)+t|0,n+=(r&t|~r&e)+o[6]-1473231341|0,n=(n<<17|n>>>15)+r|0,e+=(n&r|~n&t)+o[7]-45705983|0,e=(e<<22|e>>>10)+n|0,t+=(e&n|~e&r)+o[8]+1770035416|0,t=(t<<7|t>>>25)+e|0,r+=(t&e|~t&n)+o[9]-1958414417|0,r=(r<<12|r>>>20)+t|0,n+=(r&t|~r&e)+o[10]-42063|0,n=(n<<17|n>>>15)+r|0,e+=(n&r|~n&t)+o[11]-1990404162|0,e=(e<<22|e>>>10)+n|0,t+=(e&n|~e&r)+o[12]+1804603682|0,t=(t<<7|t>>>25)+e|0,r+=(t&e|~t&n)+o[13]-40341101|0,r=(r<<12|r>>>20)+t|0,n+=(r&t|~r&e)+o[14]-1502002290|0,n=(n<<17|n>>>15)+r|0,e+=(n&r|~n&t)+o[15]+1236535329|0,e=(e<<22|e>>>10)+n|0,t+=(e&r|n&~r)+o[1]-165796510|0,t=(t<<5|t>>>27)+e|0,r+=(t&n|e&~n)+o[6]-1069501632|0,r=(r<<9|r>>>23)+t|0,n+=(r&e|t&~e)+o[11]+643717713|0,n=(n<<14|n>>>18)+r|0,e+=(n&t|r&~t)+o[0]-373897302|0,e=(e<<20|e>>>12)+n|0,t+=(e&r|n&~r)+o[5]-701558691|0,t=(t<<5|t>>>27)+e|0,r+=(t&n|e&~n)+o[10]+38016083|0,r=(r<<9|r>>>23)+t|0,n+=(r&e|t&~e)+o[15]-660478335|0,n=(n<<14|n>>>18)+r|0,e+=(n&t|r&~t)+o[4]-405537848|0,e=(e<<20|e>>>12)+n|0,t+=(e&r|n&~r)+o[9]+568446438|0,t=(t<<5|t>>>27)+e|0,r+=(t&n|e&~n)+o[14]-1019803690|0,r=(r<<9|r>>>23)+t|0,n+=(r&e|t&~e)+o[3]-187363961|0,n=(n<<14|n>>>18)+r|0,e+=(n&t|r&~t)+o[8]+1163531501|0,e=(e<<20|e>>>12)+n|0,t+=(e&r|n&~r)+o[13]-1444681467|0,t=(t<<5|t>>>27)+e|0,r+=(t&n|e&~n)+o[2]-51403784|0,r=(r<<9|r>>>23)+t|0,n+=(r&e|t&~e)+o[7]+1735328473|0,n=(n<<14|n>>>18)+r|0,e+=(n&t|r&~t)+o[12]-1926607734|0,e=(e<<20|e>>>12)+n|0,t+=(e^n^r)+o[5]-378558|0,t=(t<<4|t>>>28)+e|0,r+=(t^e^n)+o[8]-2022574463|0,r=(r<<11|r>>>21)+t|0,n+=(r^t^e)+o[11]+1839030562|0,n=(n<<16|n>>>16)+r|0,e+=(n^r^t)+o[14]-35309556|0,e=(e<<23|e>>>9)+n|0,t+=(e^n^r)+o[1]-1530992060|0,t=(t<<4|t>>>28)+e|0,r+=(t^e^n)+o[4]+1272893353|0,r=(r<<11|r>>>21)+t|0,n+=(r^t^e)+o[7]-155497632|0,n=(n<<16|n>>>16)+r|0,e+=(n^r^t)+o[10]-1094730640|0,e=(e<<23|e>>>9)+n|0,t+=(e^n^r)+o[13]+681279174|0,t=(t<<4|t>>>28)+e|0,r+=(t^e^n)+o[0]-358537222|0,r=(r<<11|r>>>21)+t|0,n+=(r^t^e)+o[3]-722521979|0,n=(n<<16|n>>>16)+r|0,e+=(n^r^t)+o[6]+76029189|0,e=(e<<23|e>>>9)+n|0,t+=(e^n^r)+o[9]-640364487|0,t=(t<<4|t>>>28)+e|0,r+=(t^e^n)+o[12]-421815835|0,r=(r<<11|r>>>21)+t|0,n+=(r^t^e)+o[15]+530742520|0,n=(n<<16|n>>>16)+r|0,e+=(n^r^t)+o[2]-995338651|0,e=(e<<23|e>>>9)+n|0,t+=(n^(e|~r))+o[0]-198630844|0,t=(t<<6|t>>>26)+e|0,r+=(e^(t|~n))+o[7]+1126891415|0,r=(r<<10|r>>>22)+t|0,n+=(t^(r|~e))+o[14]-1416354905|0,n=(n<<15|n>>>17)+r|0,e+=(r^(n|~t))+o[5]-57434055|0,e=(e<<21|e>>>11)+n|0,t+=(n^(e|~r))+o[12]+1700485571|0,t=(t<<6|t>>>26)+e|0,r+=(e^(t|~n))+o[3]-1894986606|0,r=(r<<10|r>>>22)+t|0,n+=(t^(r|~e))+o[10]-1051523|0,n=(n<<15|n>>>17)+r|0,e+=(r^(n|~t))+o[1]-2054922799|0,e=(e<<21|e>>>11)+n|0,t+=(n^(e|~r))+o[8]+1873313359|0,t=(t<<6|t>>>26)+e|0,r+=(e^(t|~n))+o[15]-30611744|0,r=(r<<10|r>>>22)+t|0,n+=(t^(r|~e))+o[6]-1560198380|0,n=(n<<15|n>>>17)+r|0,e+=(r^(n|~t))+o[13]+1309151649|0,e=(e<<21|e>>>11)+n|0,t+=(n^(e|~r))+o[4]-145523070|0,t=(t<<6|t>>>26)+e|0,r+=(e^(t|~n))+o[11]-1120210379|0,r=(r<<10|r>>>22)+t|0,n+=(t^(r|~e))+o[2]+718787259|0,n=(n<<15|n>>>17)+r|0,e+=(r^(n|~t))+o[9]-343485551|0,e=(e<<21|e>>>11)+n|0,i[0]=t+i[0]|0,i[1]=e+i[1]|0,i[2]=n+i[2]|0,i[3]=r+i[3]|0}function h(i){var o=[],t;for(t=0;t<64;t+=4)o[t>>2]=i.charCodeAt(t)+(i.charCodeAt(t+1)<<8)+(i.charCodeAt(t+2)<<16)+(i.charCodeAt(t+3)<<24);return o}function u(i){var o=[],t;for(t=0;t<64;t+=4)o[t>>2]=i[t]+(i[t+1]<<8)+(i[t+2]<<16)+(i[t+3]<<24);return o}function w(i){var o=i.length,t=[1732584193,-271733879,-1732584194,271733878],e,n,r,p,b,S;for(e=64;e<=o;e+=64)l(t,h(i.substring(e-64,e)));for(i=i.substring(e-64),n=i.length,r=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],e=0;e<n;e+=1)r[e>>2]|=i.charCodeAt(e)<<(e%4<<3);if(r[e>>2]|=128<<(e%4<<3),e>55)for(l(t,r),e=0;e<16;e+=1)r[e]=0;return p=o*8,p=p.toString(16).match(/(.*?)(.{0,8})$/),b=parseInt(p[2],16),S=parseInt(p[1],16)||0,r[14]=b,r[15]=S,l(t,r),t}function g(i){var o=i.length,t=[1732584193,-271733879,-1732584194,271733878],e,n,r,p,b,S;for(e=64;e<=o;e+=64)l(t,u(i.subarray(e-64,e)));for(i=e-64<o?i.subarray(e-64):new Uint8Array(0),n=i.length,r=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],e=0;e<n;e+=1)r[e>>2]|=i[e]<<(e%4<<3);if(r[e>>2]|=128<<(e%4<<3),e>55)for(l(t,r),e=0;e<16;e+=1)r[e]=0;return p=o*8,p=p.toString(16).match(/(.*?)(.{0,8})$/),b=parseInt(p[2],16),S=parseInt(p[1],16)||0,r[14]=b,r[15]=S,l(t,r),t}function $(i){var o="",t;for(t=0;t<4;t+=1)o+=c[i>>t*8+4&15]+c[i>>t*8&15];return o}function C(i){var o;for(o=0;o<i.length;o+=1)i[o]=$(i[o]);return i.join("")}C(w("hello")),typeof ArrayBuffer<"u"&&!ArrayBuffer.prototype.slice&&function(){function i(o,t){return o=o|0||0,o<0?Math.max(o+t,0):Math.min(o,t)}ArrayBuffer.prototype.slice=function(o,t){var e=this.byteLength,n=i(o,e),r=e,p,b,S,R;return t!==s&&(r=i(t,e)),n>r?new ArrayBuffer(0):(p=r-n,b=new ArrayBuffer(p),S=new Uint8Array(b),R=new Uint8Array(this,n,p),S.set(R),b)}}();function v(i){return/[\u0080-\uFFFF]/.test(i)&&(i=unescape(encodeURIComponent(i))),i}function m(i,o){var t=i.length,e=new ArrayBuffer(t),n=new Uint8Array(e),r;for(r=0;r<t;r+=1)n[r]=i.charCodeAt(r);return o?n:e}function _(i){return String.fromCharCode.apply(null,new Uint8Array(i))}function A(i,o,t){var e=new Uint8Array(i.byteLength+o.byteLength);return e.set(new Uint8Array(i)),e.set(new Uint8Array(o),i.byteLength),e}function y(i){var o=[],t=i.length,e;for(e=0;e<t-1;e+=2)o.push(parseInt(i.substr(e,2),16));return String.fromCharCode.apply(String,o)}function d(){this.reset()}return d.prototype.append=function(i){return this.appendBinary(v(i)),this},d.prototype.appendBinary=function(i){this._buff+=i,this._length+=i.length;var o=this._buff.length,t;for(t=64;t<=o;t+=64)l(this._hash,h(this._buff.substring(t-64,t)));return this._buff=this._buff.substring(t-64),this},d.prototype.end=function(i){var o=this._buff,t=o.length,e,n=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],r;for(e=0;e<t;e+=1)n[e>>2]|=o.charCodeAt(e)<<(e%4<<3);return this._finish(n,t),r=C(this._hash),i&&(r=y(r)),this.reset(),r},d.prototype.reset=function(){return this._buff="",this._length=0,this._hash=[1732584193,-271733879,-1732584194,271733878],this},d.prototype.getState=function(){return{buff:this._buff,length:this._length,hash:this._hash.slice()}},d.prototype.setState=function(i){return this._buff=i.buff,this._length=i.length,this._hash=i.hash,this},d.prototype.destroy=function(){delete this._hash,delete this._buff,delete this._length},d.prototype._finish=function(i,o){var t=o,e,n,r;if(i[t>>2]|=128<<(t%4<<3),t>55)for(l(this._hash,i),t=0;t<16;t+=1)i[t]=0;e=this._length*8,e=e.toString(16).match(/(.*?)(.{0,8})$/),n=parseInt(e[2],16),r=parseInt(e[1],16)||0,i[14]=n,i[15]=r,l(this._hash,i)},d.hash=function(i,o){return d.hashBinary(v(i),o)},d.hashBinary=function(i,o){var t=w(i),e=C(t);return o?y(e):e},d.ArrayBuffer=function(){this.reset()},d.ArrayBuffer.prototype.append=function(i){var o=A(this._buff.buffer,i),t=o.length,e;for(this._length+=i.byteLength,e=64;e<=t;e+=64)l(this._hash,u(o.subarray(e-64,e)));return this._buff=e-64<t?new Uint8Array(o.buffer.slice(e-64)):new Uint8Array(0),this},d.ArrayBuffer.prototype.end=function(i){var o=this._buff,t=o.length,e=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],n,r;for(n=0;n<t;n+=1)e[n>>2]|=o[n]<<(n%4<<3);return this._finish(e,t),r=C(this._hash),i&&(r=y(r)),this.reset(),r},d.ArrayBuffer.prototype.reset=function(){return this._buff=new Uint8Array(0),this._length=0,this._hash=[1732584193,-271733879,-1732584194,271733878],this},d.ArrayBuffer.prototype.getState=function(){var i=d.prototype.getState.call(this);return i.buff=_(i.buff),i},d.ArrayBuffer.prototype.setState=function(i){return i.buff=m(i.buff,!0),d.prototype.setState.call(this,i)},d.ArrayBuffer.prototype.destroy=d.prototype.destroy,d.ArrayBuffer.prototype._finish=d.prototype._finish,d.ArrayBuffer.hash=function(i,o){var t=g(new Uint8Array(i)),e=C(t);return o?y(e):e},d})})(H);var q=H.exports;const G=K(q),B="xibo-media-v1",Z="xibo-player",V=1,x="files",M=4,k=typeof window<"u"&&window.location.pathname.replace(/\/[^/]*$/,"").replace(/\/$/,"")||"/player/pwa";class Y{constructor(){this.cache=null,this.db=null,this.dependants=new Map}extractFilename(a){try{return new URL(a).searchParams.get("file")||"unknown"}catch{return"unknown"}}rewriteUrl(a){if(!a)return a;try{const s=new URL(a),c=new URL(N.cmsAddress);if(s.origin!==c.origin)return console.log(`[Cache] Rewriting URL: ${s.origin} → ${c.origin}`),s.protocol=c.protocol,s.hostname=c.hostname,s.port=c.port,s.toString()}catch{}return a}async init(){this.cache=await caches.open(B),this.db=await this.openDB()}openDB(){return new Promise((a,s)=>{const c=indexedDB.open(Z,V);c.onerror=()=>s(c.error),c.onsuccess=()=>a(c.result),c.onupgradeneeded=l=>{const h=l.target.result;h.objectStoreNames.contains(x)||h.createObjectStore(x,{keyPath:"id"}).createIndex("type","type",{unique:!1})}})}async getFile(a){return new Promise((s,c)=>{const u=this.db.transaction(x,"readonly").objectStore(x).get(a);u.onsuccess=()=>s(u.result),u.onerror=()=>c(u.error)})}async saveFile(a){return new Promise((s,c)=>{const u=this.db.transaction(x,"readwrite").objectStore(x).put(a);u.onsuccess=()=>s(),u.onerror=()=>c(u.error)})}async getAllFiles(){return new Promise((a,s)=>{const h=this.db.transaction(x,"readonly").objectStore(x).getAll();h.onsuccess=()=>a(h.result),h.onerror=()=>s(h.error)})}async downloadFile(a){var i;const{id:s,type:c,path:l,md5:h,download:u}=a;if(typeof navigator<"u"&&((i=navigator.serviceWorker)!=null&&i.controller))return console.log(`[Cache] Service Worker active - skipping direct download for ${c}/${s}`),console.log("[Cache] File will be downloaded by Service Worker in background"),{id:s,type:c,path:l,md5:h||"pending",size:0,cachedAt:Date.now(),isServiceWorkerDownload:!0};if(!l||l==="null"||l==="undefined")return console.log(`[Cache] Skipping ${c}/${s} - no download URL (will be generated on-demand)`),null;const w=await this.getFile(s),g=this.getCacheKey(c,s);if(w)if(w.md5===h){const o=await this.cache.match(g);if(o&&c==="media"){const t=await o.blob(),e=o.headers.get("Content-Type");if(e==="text/plain"||t.size<100)console.warn(`[Cache] Bad cache detected for ${c}/${s} (${e}, ${t.size} bytes) - re-downloading`),await this.cache.delete(g);else return console.log(`[Cache] ${c}/${s} already cached`),w}else return console.log(`[Cache] ${c}/${s} already cached`),w}else console.warn(`[Cache] ${c}/${s} MD5 changed (cached: ${w.md5}, expected: ${h}) - re-downloading`),await this.cache.delete(g);console.log(`[Cache] Downloading ${c}/${s} from ${l}`);const $=this.rewriteUrl(l);console.log(`[Cache] Using URL: ${$}`);const C=await fetch($,{method:"HEAD"});if(C.status===202)return console.warn(`[Cache] ${c}/${s} still downloading in background (HTTP 202) - will retry on next collection`),{id:s,type:c,path:l,md5:h||"pending",size:0,cachedAt:Date.now(),isPending:!0};const v=parseInt(C.headers.get("Content-Length")||"0"),m=v>100*1024*1024;console.log(`[Cache] File size: ${(v/1024/1024).toFixed(1)} MB ${m?"(large file)":""}`);const _=c==="media"?this.extractFilename(l):s;let A,y;if(m){console.log(`[Cache] Large file detected (${(v/1024/1024).toFixed(1)} MB), caching in background`),this.downloadLargeFileInBackground($,g,v,_,s,c,l,h).catch(t=>console.warn(`[Cache] Background download failed for ${s}:`,t));const o={id:s,type:c,path:l,md5:h||"pending",size:v,cachedAt:Date.now(),isBackgroundDownload:!0};return await this.saveFile(o),console.log(`[Cache] ${c}/${s} downloading in background (${v} bytes)`),o}else{this.notifyDownloadProgress(_,0,v);const o=await fetch($);if(o.status===202)return console.warn(`[Cache] ${c}/${s} still downloading in background (HTTP 202) - will retry on next collection`),{id:s,type:c,path:l,md5:h||"pending",size:0,cachedAt:Date.now(),isPending:!0};if(!o.ok)throw new Error(`Failed to download ${l}: ${o.status}`);const t=await o.blob(),e=await t.arrayBuffer();A=G.ArrayBuffer.hash(e),h&&A!==h&&(console.warn(`[Cache] MD5 mismatch for ${c}/${s}:`),console.warn(`[Cache] Expected: ${h}`),console.warn(`[Cache] Got: ${A}`),console.warn("[Cache] Accepting file anyway (kiosk mode - renderer will validate)"),A=h),await this.cache.put(g,new Response(t,{headers:{"Content-Type":o.headers.get("Content-Type")||"application/octet-stream","Content-Length":t.size}})),y=t.size,this.notifyDownloadProgress(_,y,v,!0),console.log(`[Cache] Cached ${c}/${s} (${y} bytes, MD5: ${A})`)}const d={id:s,type:c,path:l,md5:A,size:y,cachedAt:Date.now()};return await this.saveFile(d),d}getCacheKey(a,s,c=null){return`${k}/cache/${a}/${c||s}`}async getCachedFile(a,s){const c=this.getCacheKey(a,s),l=await this.cache.match(c);return l?await l.blob():null}async getCachedResponse(a,s){const c=this.getCacheKey(a,s);return await this.cache.match(c)}async getCachedFileText(a,s){const c=this.getCacheKey(a,s),l=await this.cache.match(c);return l?await l.text():null}async cacheWidgetHtml(a,s,c,l){const h=`${k}/cache/widget/${a}/${s}/${c}`,u=await caches.open(B),w='<base href="/player/cache/media/">';let g=l;l.includes("<head>")?g=l.replace("<head>","<head>"+w):l.includes("<HEAD>")?g=l.replace("<HEAD>","<HEAD>"+w):g=w+l;const $=/https?:\/\/[^"'\s)]+xmds\.php\?[^"'\s)]*file=([^&"'\s)]+)[^"'\s)]*/g,C=[];g=g.replace($,(A,y)=>{const d=`${k}/cache/static/${y}`;return C.push({filename:y,originalUrl:A}),console.log(`[Cache] Rewrote widget URL: ${y} → ${d}`),d});const v="<style>img,video{object-position:center center}</style>";g.includes("</head>")?g=g.replace("</head>",v+"</head>"):g.includes("</HEAD>")&&(g=g.replace("</HEAD>",v+"</HEAD>")),g=g.replace(/hostAddress\s*:\s*["']https?:\/\/[^"']+["']/g,`hostAddress: "${k}/ic"`),console.log("[Cache] Injected base tag and rewrote CMS URLs in widget HTML");const m=new URL(h,window.location.origin),_=new Response(g,{headers:{"Content-Type":"text/html; charset=utf-8","Access-Control-Allow-Origin":"*"}});if(await u.put(m,_),console.log(`[Cache] Stored widget HTML at ${h} (${g.length} bytes)`),C.length>0){const y=await caches.open("xibo-static-v1");await Promise.all(C.map(async({filename:d,originalUrl:i})=>{const o=`${k}/cache/static/${d}`;if(!await y.match(o))try{const e=await fetch(i);if(!e.ok){console.warn(`[Cache] Failed to fetch static resource: ${d} (HTTP ${e.status})`);return}const n=d.split(".").pop().toLowerCase(),r={js:"application/javascript",css:"text/css",otf:"font/otf",ttf:"font/ttf",woff:"font/woff",woff2:"font/woff2",eot:"application/vnd.ms-fontobject",svg:"image/svg+xml"}[n]||"application/octet-stream";if(n==="css"){let p=await e.text();const b=[],S=/url\((['"]?)(https?:\/\/[^'")\s]+\?[^'")\s]*file=([^&'")\s]+\.(?:woff2?|ttf|otf|eot|svg))[^'")\s]*)\1\)/gi;p=p.replace(S,(R,P,W,U)=>(b.push({filename:U,originalUrl:W}),console.log(`[Cache] Rewrote font URL in CSS: ${U}`),`url(${P}${k}/cache/static/${encodeURIComponent(U)}${P})`)),await y.put(o,new Response(p,{headers:{"Content-Type":"text/css"}})),console.log(`[Cache] Cached CSS with ${b.length} rewritten font URLs: ${d}`),await Promise.all(b.map(async({filename:R,originalUrl:P})=>{const W=`${k}/cache/static/${encodeURIComponent(R)}`;if(!await y.match(W))try{const D=await fetch(P);if(!D.ok){console.warn(`[Cache] Failed to fetch font: ${R} (HTTP ${D.status})`);return}const T=await D.blob(),O=R.split(".").pop().toLowerCase(),F={otf:"font/otf",ttf:"font/ttf",woff:"font/woff",woff2:"font/woff2",eot:"application/vnd.ms-fontobject",svg:"image/svg+xml"}[O]||"application/octet-stream";await y.put(W,new Response(T,{headers:{"Content-Type":F}})),console.log(`[Cache] Cached font: ${R} (${F}, ${T.size} bytes)`)}catch(D){console.warn(`[Cache] Failed to cache font: ${R}`,D)}}))}else{const p=await e.blob();await y.put(o,new Response(p,{headers:{"Content-Type":r}})),console.log(`[Cache] Cached static resource: ${d} (${r}, ${p.size} bytes)`)}}catch(e){console.warn(`[Cache] Failed to cache static resource: ${d}`,e)}}))}return h}addDependant(a,s){const c=String(a);this.dependants.has(c)||this.dependants.set(c,new Set),this.dependants.get(c).add(String(s))}removeLayoutDependants(a){const s=String(a),c=[];for(const[l,h]of this.dependants)h.delete(s),h.size===0&&(this.dependants.delete(l),c.push(l));return c.length>0&&console.log(`[Cache] ${c.length} media files orphaned after layout ${a} removed:`,c),c}isMediaReferenced(a){const s=this.dependants.get(String(a));return s?s.size>0:!1}async downloadLargeFileInBackground(a,s,c,l,h,u,w,g){var v;let C=0;console.log(`[Cache] Background download started: ${l}`),this.notifyDownloadProgress(l,0,c);try{const m=[];for(let r=0;r<c;r+=52428800){const p=Math.min(r+52428800-1,c-1);m.push({start:r,end:p,index:m.length})}console.log(`[Cache] Downloading ${m.length} chunks in parallel (${M} concurrent)`);const _=new Map;let A=0;const y=async r=>{const p=`bytes=${r.start}-${r.end}`;try{const b=await fetch(a,{headers:{Range:p}});if(!b.ok&&b.status!==206)throw new Error(`Chunk ${r.index} failed: ${b.status}`);const S=await b.blob();_.set(r.index,S),C+=S.size;const R=(C/c*100).toFixed(1);return console.log(`[Cache] Chunk ${r.index}/${m.length-1} complete (${R}%)`),this.notifyDownloadProgress(l,C,c),S}catch(b){throw console.error(`[Cache] Chunk ${r.index} failed:`,b),b}},d=async()=>{for(;A<m.length;){const r=m[A++];await y(r)}},i=[];for(let r=0;r<M;r++)i.push(d());await Promise.all(i);const o=[];for(let r=0;r<m.length;r++)o.push(_.get(r));const t=new Blob(o),e=((v=o[0])==null?void 0:v.type)||"video/mp4";await this.cache.put(s,new Response(t,{headers:{"Content-Type":e,"Content-Length":t.size,"Accept-Ranges":"bytes"}}));const n={id:h,type:u,path:w,md5:g||"background",size:t.size,cachedAt:Date.now(),isBackgroundDownload:!1,cached:!0};await this.saveFile(n),this.notifyDownloadProgress(l,C,c,!0),console.log(`[Cache] Background download complete: ${l} (${t.size} bytes in ${o.length} chunks)`),window.dispatchEvent(new CustomEvent("media-cached",{detail:{filename:l,id:h,type:u,size:t.size}}))}catch(m){console.error(`[Cache] Background download failed for ${l}:`,m),this.notifyDownloadProgress(l,C,c,!1,!0)}}notifyDownloadProgress(a,s,c,l=!1,h=!1){const u=new CustomEvent("download-progress",{detail:{filename:a,loaded:s,total:c,percent:c>0?s/c*100:0,complete:l,error:h}});window.dispatchEvent(u)}async clearAll(){return await caches.delete(B),this.cache=await caches.open(B),new Promise((a,s)=>{const h=this.db.transaction(x,"readwrite").objectStore(x).clear();h.onsuccess=()=>a(),h.onerror=()=>s(h.error)})}}const X=new Y,f=j("CacheProxy"),z=typeof window<"u"&&window.location.pathname.replace(/\/[^/]*$/,"").replace(/\/$/,"")||"/player/pwa";class L extends I{constructor(){super(),this.controller=null,this.fetchReady=!1,this.fetchReadyPromise=null,this.fetchReadyResolve=null}async init(){if(this.fetchReadyPromise=new Promise(a=>{this.fetchReadyResolve=a}),navigator.serviceWorker.addEventListener("message",a=>{var s;((s=a.data)==null?void 0:s.type)==="SW_READY"&&(f.info("Received SW_READY signal - fetch handler is ready"),this.fetchReady=!0,this.fetchReadyResolve())}),"serviceWorker"in navigator){const a=await navigator.serviceWorker.getRegistration();if(a&&a.active&&a.active.state==="activated"){f.info("Using active Service Worker (controller not required)"),this.controller=navigator.serviceWorker.controller||a.active,this.controller.postMessage({type:"PING"}),f.info("Service Worker backend initialized, waiting for fetch readiness...");return}if(await navigator.serviceWorker.ready,this.controller=navigator.serviceWorker.controller,!this.controller)throw new Error("Service Worker not controlling page");this.controller.postMessage({type:"PING"}),f.info("Service Worker backend initialized, waiting for fetch readiness...")}else throw new Error("Service Worker not supported")}async getFile(a,s){this.fetchReady||(f.debug(`Waiting for SW fetch handler to be ready before fetching ${a}/${s}...`),await this.fetchReadyPromise,f.debug("SW fetch handler ready, proceeding with fetch"));const c=`${z}/cache/${a}/${s}`;f.debug(`getFile(${a}, ${s}) → fetching ${c}`),f.debug("About to call fetch()...");try{f.debug(`Calling fetch(${c})...`);const l=await fetch(c);if(f.debug("fetch returned, status:",l.status,l.statusText),!l.ok){if(f.debug(`Response not OK (${l.status}), returning null`),l.status===404)return null;throw new Error(`Failed to get file: ${l.status}`)}f.debug("Response OK, getting blob...");const h=await l.blob();return f.debug("Got blob, size:",h.size),h}catch(l){return f.error("getFile EXCEPTION:",l),f.error("Error name:",l.name),f.error("Error message:",l.message),null}}async hasFile(a,s){this.fetchReady||await this.fetchReadyPromise;const c=`${z}/cache/${a}/${s}`;try{return(await fetch(c,{method:"HEAD"})).ok}catch{return!1}}async requestDownload(a){if(!this.controller)throw new Error("Service Worker not available");const s=Array.isArray(a)?{files:a}:a;return new Promise((c,l)=>{const h=new MessageChannel;h.port1.onmessage=u=>{const{success:w,error:g,enqueuedCount:$,activeCount:C,queuedCount:v}=u.data;w?(f.info("Download request acknowledged:",$,"files"),f.info("Queue state:",C,"active,",v,"queued"),c()):l(new Error(g||"Service Worker download failed"))},this.controller.postMessage({type:"DOWNLOAD_FILES",data:s},[h.port2])})}async prioritizeDownload(a,s){if(this.controller)return new Promise(c=>{const l=new MessageChannel;l.port1.onmessage=h=>c(h.data),this.controller.postMessage({type:"PRIORITIZE_DOWNLOAD",data:{fileType:a,fileId:s}},[l.port2])})}async isCached(a,s){return this.hasFile(a,s)}async prioritizeLayoutFiles(a){this.controller&&this.controller.postMessage({type:"PRIORITIZE_LAYOUT_FILES",data:{mediaIds:a}})}}class J extends I{constructor(){super(),this.backend=null,this.backendType="service-worker"}async init(){var l,h;if(!("serviceWorker"in navigator))throw new Error("Service Worker not supported - PWA requires Service Worker");f.debug("Checking Service Worker state..."),f.debug("controller =",navigator.serviceWorker.controller);const a=await navigator.serviceWorker.getRegistration();if(f.debug("registration =",a),f.debug("active =",a==null?void 0:a.active),f.debug("installing =",a==null?void 0:a.installing),f.debug("waiting =",a==null?void 0:a.waiting),a&&a.active&&!a.installing&&!a.waiting){f.info("Active Service Worker found (no updates pending)"),f.debug("SW state =",a.active.state),navigator.serviceWorker.controller||(f.debug("Not controlling yet, waiting 200ms for claim..."),await new Promise(u=>setTimeout(u,200)),f.debug("After wait, controller =",navigator.serviceWorker.controller)),this.backend=new L,await this.backend.init(),f.info("Service Worker backend ready (fast path)");return}a&&(a.installing||a.waiting)&&(f.info("New Service Worker detected, waiting for it to activate..."),f.debug("installing =",(l=a.installing)==null?void 0:l.state),f.debug("waiting =",(h=a.waiting)==null?void 0:h.state)),f.info("No active Service Worker, waiting for registration...");const s=navigator.serviceWorker.ready,c=new Promise((u,w)=>setTimeout(()=>w(new Error("Service Worker ready timeout after 10s")),1e4));try{await Promise.race([s,c]),f.debug("Service Worker ready promise resolved")}catch(u){throw f.error("Service Worker wait failed:",u),new Error("Service Worker not ready - please reload page")}await new Promise(u=>setTimeout(u,100)),f.debug("After claim wait, controller =",navigator.serviceWorker.controller),this.backend=new L,await this.backend.init(),f.info("Service Worker backend ready (slow path)")}async getFile(a,s){if(!this.backend)throw new Error("CacheProxy not initialized");return await this.backend.getFile(a,s)}async hasFile(a,s){if(!this.backend)throw new Error("CacheProxy not initialized");return await this.backend.hasFile(a,s)}async requestDownload(a){if(!this.backend)throw new Error("CacheProxy not initialized");return await this.backend.requestDownload(a)}async prioritizeDownload(a,s){var c;if((c=this.backend)!=null&&c.prioritizeDownload)return await this.backend.prioritizeDownload(a,s)}async isCached(a,s){return this.hasFile(a,s)}async prioritizeLayoutFiles(a){var s;if((s=this.backend)!=null&&s.prioritizeLayoutFiles)return await this.backend.prioritizeLayoutFiles(a)}getBackendType(){return this.backendType}isUsingServiceWorker(){return this.backendType==="service-worker"}async deleteFiles(a){if(!this.backend)throw new Error("CacheProxy not initialized");return new Promise((s,c)=>{const l=new MessageChannel;l.port1.onmessage=h=>{const{success:u,error:w,deleted:g,total:$}=h.data;u?s({deleted:g,total:$}):c(new Error(w||"Delete failed"))},navigator.serviceWorker.controller.postMessage({type:"DELETE_FILES",data:{files:a}},[l.port2]),setTimeout(()=>s({deleted:0,total:a.length}),5e3)})}async prewarmVideoChunks(a){var c;if(!a||a.length===0)return{warmed:0,total:0};const s=(c=navigator.serviceWorker)==null?void 0:c.controller;return s?new Promise(l=>{const h=new MessageChannel;h.port1.onmessage=u=>{const{success:w,warmed:g,total:$}=u.data;l(w?{warmed:g,total:$}:{warmed:0,total:a.length})},s.postMessage({type:"PREWARM_VIDEO_CHUNKS",data:{mediaIds:a}},[h.port2]),setTimeout(()=>l({warmed:0,total:a.length}),2e3)}):{warmed:0,total:a.length}}async getDownloadProgress(){if(!this.backend)throw new Error("CacheProxy not initialized");return new Promise(a=>{const s=new MessageChannel;s.port1.onmessage=c=>{const{success:l,progress:h}=c.data;a(l?h:{})},navigator.serviceWorker.controller.postMessage({type:"GET_DOWNLOAD_PROGRESS"},[s.port2]),setTimeout(()=>a({}),1e3)})}}export{J as C,I as E,Y as a,X as c};
|
|
2
|
+
//# sourceMappingURL=cache-proxy-CrlayfSe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache-proxy-CrlayfSe.js","sources":["../../../xiboplayer/packages/utils/src/event-emitter.js","../../../xiboplayer/node_modules/.pnpm/spark-md5@3.0.2/node_modules/spark-md5/spark-md5.js","../../../xiboplayer/packages/cache/src/cache.js","../../../xiboplayer/packages/cache/src/cache-proxy.js"],"sourcesContent":["/**\n * Simple EventEmitter implementation\n * Compatible with both browser and Node.js\n */\n\nexport class EventEmitter {\n constructor() {\n this.events = new Map();\n }\n\n /**\n * Register event listener\n * @param {string} event - Event name\n * @param {Function} callback - Callback function\n */\n on(event, callback) {\n if (!this.events.has(event)) {\n this.events.set(event, []);\n }\n this.events.get(event).push(callback);\n }\n\n /**\n * Register one-time event listener\n * @param {string} event - Event name\n * @param {Function} callback - Callback function\n */\n once(event, callback) {\n const wrapper = (...args) => {\n callback(...args);\n this.off(event, wrapper);\n };\n this.on(event, wrapper);\n }\n\n /**\n * Remove event listener\n * @param {string} event - Event name\n * @param {Function} callback - Callback function\n */\n off(event, callback) {\n if (!this.events.has(event)) return;\n\n const listeners = this.events.get(event);\n const index = listeners.indexOf(callback);\n if (index !== -1) {\n listeners.splice(index, 1);\n }\n }\n\n /**\n * Emit event\n * @param {string} event - Event name\n * @param {...any} args - Arguments to pass to listeners\n */\n emit(event, ...args) {\n if (!this.events.has(event)) return;\n\n // Make a copy to handle listeners that remove themselves during emission\n const listeners = this.events.get(event).slice();\n for (const listener of listeners) {\n listener(...args);\n }\n }\n\n /**\n * Remove all listeners for an event\n * @param {string} event - Event name (optional, removes all if not specified)\n */\n removeAllListeners(event) {\n if (event) {\n this.events.delete(event);\n } else {\n this.events.clear();\n }\n }\n}\n","(function (factory) {\n if (typeof exports === 'object') {\n // Node/CommonJS\n module.exports = factory();\n } else if (typeof define === 'function' && define.amd) {\n // AMD\n define(factory);\n } else {\n // Browser globals (with support for web workers)\n var glob;\n\n try {\n glob = window;\n } catch (e) {\n glob = self;\n }\n\n glob.SparkMD5 = factory();\n }\n}(function (undefined) {\n\n 'use strict';\n\n /*\n * Fastest md5 implementation around (JKM md5).\n * Credits: Joseph Myers\n *\n * @see http://www.myersdaily.org/joseph/javascript/md5-text.html\n * @see http://jsperf.com/md5-shootout/7\n */\n\n /* this function is much faster,\n so if possible we use it. Some IEs\n are the only ones I know of that\n need the idiotic second function,\n generated by an if clause. */\n var add32 = function (a, b) {\n return (a + b) & 0xFFFFFFFF;\n },\n hex_chr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'];\n\n\n function cmn(q, a, b, x, s, t) {\n a = add32(add32(a, q), add32(x, t));\n return add32((a << s) | (a >>> (32 - s)), b);\n }\n\n function md5cycle(x, k) {\n var a = x[0],\n b = x[1],\n c = x[2],\n d = x[3];\n\n a += (b & c | ~b & d) + k[0] - 680876936 | 0;\n a = (a << 7 | a >>> 25) + b | 0;\n d += (a & b | ~a & c) + k[1] - 389564586 | 0;\n d = (d << 12 | d >>> 20) + a | 0;\n c += (d & a | ~d & b) + k[2] + 606105819 | 0;\n c = (c << 17 | c >>> 15) + d | 0;\n b += (c & d | ~c & a) + k[3] - 1044525330 | 0;\n b = (b << 22 | b >>> 10) + c | 0;\n a += (b & c | ~b & d) + k[4] - 176418897 | 0;\n a = (a << 7 | a >>> 25) + b | 0;\n d += (a & b | ~a & c) + k[5] + 1200080426 | 0;\n d = (d << 12 | d >>> 20) + a | 0;\n c += (d & a | ~d & b) + k[6] - 1473231341 | 0;\n c = (c << 17 | c >>> 15) + d | 0;\n b += (c & d | ~c & a) + k[7] - 45705983 | 0;\n b = (b << 22 | b >>> 10) + c | 0;\n a += (b & c | ~b & d) + k[8] + 1770035416 | 0;\n a = (a << 7 | a >>> 25) + b | 0;\n d += (a & b | ~a & c) + k[9] - 1958414417 | 0;\n d = (d << 12 | d >>> 20) + a | 0;\n c += (d & a | ~d & b) + k[10] - 42063 | 0;\n c = (c << 17 | c >>> 15) + d | 0;\n b += (c & d | ~c & a) + k[11] - 1990404162 | 0;\n b = (b << 22 | b >>> 10) + c | 0;\n a += (b & c | ~b & d) + k[12] + 1804603682 | 0;\n a = (a << 7 | a >>> 25) + b | 0;\n d += (a & b | ~a & c) + k[13] - 40341101 | 0;\n d = (d << 12 | d >>> 20) + a | 0;\n c += (d & a | ~d & b) + k[14] - 1502002290 | 0;\n c = (c << 17 | c >>> 15) + d | 0;\n b += (c & d | ~c & a) + k[15] + 1236535329 | 0;\n b = (b << 22 | b >>> 10) + c | 0;\n\n a += (b & d | c & ~d) + k[1] - 165796510 | 0;\n a = (a << 5 | a >>> 27) + b | 0;\n d += (a & c | b & ~c) + k[6] - 1069501632 | 0;\n d = (d << 9 | d >>> 23) + a | 0;\n c += (d & b | a & ~b) + k[11] + 643717713 | 0;\n c = (c << 14 | c >>> 18) + d | 0;\n b += (c & a | d & ~a) + k[0] - 373897302 | 0;\n b = (b << 20 | b >>> 12) + c | 0;\n a += (b & d | c & ~d) + k[5] - 701558691 | 0;\n a = (a << 5 | a >>> 27) + b | 0;\n d += (a & c | b & ~c) + k[10] + 38016083 | 0;\n d = (d << 9 | d >>> 23) + a | 0;\n c += (d & b | a & ~b) + k[15] - 660478335 | 0;\n c = (c << 14 | c >>> 18) + d | 0;\n b += (c & a | d & ~a) + k[4] - 405537848 | 0;\n b = (b << 20 | b >>> 12) + c | 0;\n a += (b & d | c & ~d) + k[9] + 568446438 | 0;\n a = (a << 5 | a >>> 27) + b | 0;\n d += (a & c | b & ~c) + k[14] - 1019803690 | 0;\n d = (d << 9 | d >>> 23) + a | 0;\n c += (d & b | a & ~b) + k[3] - 187363961 | 0;\n c = (c << 14 | c >>> 18) + d | 0;\n b += (c & a | d & ~a) + k[8] + 1163531501 | 0;\n b = (b << 20 | b >>> 12) + c | 0;\n a += (b & d | c & ~d) + k[13] - 1444681467 | 0;\n a = (a << 5 | a >>> 27) + b | 0;\n d += (a & c | b & ~c) + k[2] - 51403784 | 0;\n d = (d << 9 | d >>> 23) + a | 0;\n c += (d & b | a & ~b) + k[7] + 1735328473 | 0;\n c = (c << 14 | c >>> 18) + d | 0;\n b += (c & a | d & ~a) + k[12] - 1926607734 | 0;\n b = (b << 20 | b >>> 12) + c | 0;\n\n a += (b ^ c ^ d) + k[5] - 378558 | 0;\n a = (a << 4 | a >>> 28) + b | 0;\n d += (a ^ b ^ c) + k[8] - 2022574463 | 0;\n d = (d << 11 | d >>> 21) + a | 0;\n c += (d ^ a ^ b) + k[11] + 1839030562 | 0;\n c = (c << 16 | c >>> 16) + d | 0;\n b += (c ^ d ^ a) + k[14] - 35309556 | 0;\n b = (b << 23 | b >>> 9) + c | 0;\n a += (b ^ c ^ d) + k[1] - 1530992060 | 0;\n a = (a << 4 | a >>> 28) + b | 0;\n d += (a ^ b ^ c) + k[4] + 1272893353 | 0;\n d = (d << 11 | d >>> 21) + a | 0;\n c += (d ^ a ^ b) + k[7] - 155497632 | 0;\n c = (c << 16 | c >>> 16) + d | 0;\n b += (c ^ d ^ a) + k[10] - 1094730640 | 0;\n b = (b << 23 | b >>> 9) + c | 0;\n a += (b ^ c ^ d) + k[13] + 681279174 | 0;\n a = (a << 4 | a >>> 28) + b | 0;\n d += (a ^ b ^ c) + k[0] - 358537222 | 0;\n d = (d << 11 | d >>> 21) + a | 0;\n c += (d ^ a ^ b) + k[3] - 722521979 | 0;\n c = (c << 16 | c >>> 16) + d | 0;\n b += (c ^ d ^ a) + k[6] + 76029189 | 0;\n b = (b << 23 | b >>> 9) + c | 0;\n a += (b ^ c ^ d) + k[9] - 640364487 | 0;\n a = (a << 4 | a >>> 28) + b | 0;\n d += (a ^ b ^ c) + k[12] - 421815835 | 0;\n d = (d << 11 | d >>> 21) + a | 0;\n c += (d ^ a ^ b) + k[15] + 530742520 | 0;\n c = (c << 16 | c >>> 16) + d | 0;\n b += (c ^ d ^ a) + k[2] - 995338651 | 0;\n b = (b << 23 | b >>> 9) + c | 0;\n\n a += (c ^ (b | ~d)) + k[0] - 198630844 | 0;\n a = (a << 6 | a >>> 26) + b | 0;\n d += (b ^ (a | ~c)) + k[7] + 1126891415 | 0;\n d = (d << 10 | d >>> 22) + a | 0;\n c += (a ^ (d | ~b)) + k[14] - 1416354905 | 0;\n c = (c << 15 | c >>> 17) + d | 0;\n b += (d ^ (c | ~a)) + k[5] - 57434055 | 0;\n b = (b << 21 |b >>> 11) + c | 0;\n a += (c ^ (b | ~d)) + k[12] + 1700485571 | 0;\n a = (a << 6 | a >>> 26) + b | 0;\n d += (b ^ (a | ~c)) + k[3] - 1894986606 | 0;\n d = (d << 10 | d >>> 22) + a | 0;\n c += (a ^ (d | ~b)) + k[10] - 1051523 | 0;\n c = (c << 15 | c >>> 17) + d | 0;\n b += (d ^ (c | ~a)) + k[1] - 2054922799 | 0;\n b = (b << 21 |b >>> 11) + c | 0;\n a += (c ^ (b | ~d)) + k[8] + 1873313359 | 0;\n a = (a << 6 | a >>> 26) + b | 0;\n d += (b ^ (a | ~c)) + k[15] - 30611744 | 0;\n d = (d << 10 | d >>> 22) + a | 0;\n c += (a ^ (d | ~b)) + k[6] - 1560198380 | 0;\n c = (c << 15 | c >>> 17) + d | 0;\n b += (d ^ (c | ~a)) + k[13] + 1309151649 | 0;\n b = (b << 21 |b >>> 11) + c | 0;\n a += (c ^ (b | ~d)) + k[4] - 145523070 | 0;\n a = (a << 6 | a >>> 26) + b | 0;\n d += (b ^ (a | ~c)) + k[11] - 1120210379 | 0;\n d = (d << 10 | d >>> 22) + a | 0;\n c += (a ^ (d | ~b)) + k[2] + 718787259 | 0;\n c = (c << 15 | c >>> 17) + d | 0;\n b += (d ^ (c | ~a)) + k[9] - 343485551 | 0;\n b = (b << 21 | b >>> 11) + c | 0;\n\n x[0] = a + x[0] | 0;\n x[1] = b + x[1] | 0;\n x[2] = c + x[2] | 0;\n x[3] = d + x[3] | 0;\n }\n\n function md5blk(s) {\n var md5blks = [],\n i; /* Andy King said do it this way. */\n\n for (i = 0; i < 64; i += 4) {\n md5blks[i >> 2] = s.charCodeAt(i) + (s.charCodeAt(i + 1) << 8) + (s.charCodeAt(i + 2) << 16) + (s.charCodeAt(i + 3) << 24);\n }\n return md5blks;\n }\n\n function md5blk_array(a) {\n var md5blks = [],\n i; /* Andy King said do it this way. */\n\n for (i = 0; i < 64; i += 4) {\n md5blks[i >> 2] = a[i] + (a[i + 1] << 8) + (a[i + 2] << 16) + (a[i + 3] << 24);\n }\n return md5blks;\n }\n\n function md51(s) {\n var n = s.length,\n state = [1732584193, -271733879, -1732584194, 271733878],\n i,\n length,\n tail,\n tmp,\n lo,\n hi;\n\n for (i = 64; i <= n; i += 64) {\n md5cycle(state, md5blk(s.substring(i - 64, i)));\n }\n s = s.substring(i - 64);\n length = s.length;\n tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];\n for (i = 0; i < length; i += 1) {\n tail[i >> 2] |= s.charCodeAt(i) << ((i % 4) << 3);\n }\n tail[i >> 2] |= 0x80 << ((i % 4) << 3);\n if (i > 55) {\n md5cycle(state, tail);\n for (i = 0; i < 16; i += 1) {\n tail[i] = 0;\n }\n }\n\n // Beware that the final length might not fit in 32 bits so we take care of that\n tmp = n * 8;\n tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/);\n lo = parseInt(tmp[2], 16);\n hi = parseInt(tmp[1], 16) || 0;\n\n tail[14] = lo;\n tail[15] = hi;\n\n md5cycle(state, tail);\n return state;\n }\n\n function md51_array(a) {\n var n = a.length,\n state = [1732584193, -271733879, -1732584194, 271733878],\n i,\n length,\n tail,\n tmp,\n lo,\n hi;\n\n for (i = 64; i <= n; i += 64) {\n md5cycle(state, md5blk_array(a.subarray(i - 64, i)));\n }\n\n // Not sure if it is a bug, however IE10 will always produce a sub array of length 1\n // containing the last element of the parent array if the sub array specified starts\n // beyond the length of the parent array - weird.\n // https://connect.microsoft.com/IE/feedback/details/771452/typed-array-subarray-issue\n a = (i - 64) < n ? a.subarray(i - 64) : new Uint8Array(0);\n\n length = a.length;\n tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];\n for (i = 0; i < length; i += 1) {\n tail[i >> 2] |= a[i] << ((i % 4) << 3);\n }\n\n tail[i >> 2] |= 0x80 << ((i % 4) << 3);\n if (i > 55) {\n md5cycle(state, tail);\n for (i = 0; i < 16; i += 1) {\n tail[i] = 0;\n }\n }\n\n // Beware that the final length might not fit in 32 bits so we take care of that\n tmp = n * 8;\n tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/);\n lo = parseInt(tmp[2], 16);\n hi = parseInt(tmp[1], 16) || 0;\n\n tail[14] = lo;\n tail[15] = hi;\n\n md5cycle(state, tail);\n\n return state;\n }\n\n function rhex(n) {\n var s = '',\n j;\n for (j = 0; j < 4; j += 1) {\n s += hex_chr[(n >> (j * 8 + 4)) & 0x0F] + hex_chr[(n >> (j * 8)) & 0x0F];\n }\n return s;\n }\n\n function hex(x) {\n var i;\n for (i = 0; i < x.length; i += 1) {\n x[i] = rhex(x[i]);\n }\n return x.join('');\n }\n\n // In some cases the fast add32 function cannot be used..\n if (hex(md51('hello')) !== '5d41402abc4b2a76b9719d911017c592') {\n add32 = function (x, y) {\n var lsw = (x & 0xFFFF) + (y & 0xFFFF),\n msw = (x >> 16) + (y >> 16) + (lsw >> 16);\n return (msw << 16) | (lsw & 0xFFFF);\n };\n }\n\n // ---------------------------------------------------\n\n /**\n * ArrayBuffer slice polyfill.\n *\n * @see https://github.com/ttaubert/node-arraybuffer-slice\n */\n\n if (typeof ArrayBuffer !== 'undefined' && !ArrayBuffer.prototype.slice) {\n (function () {\n function clamp(val, length) {\n val = (val | 0) || 0;\n\n if (val < 0) {\n return Math.max(val + length, 0);\n }\n\n return Math.min(val, length);\n }\n\n ArrayBuffer.prototype.slice = function (from, to) {\n var length = this.byteLength,\n begin = clamp(from, length),\n end = length,\n num,\n target,\n targetArray,\n sourceArray;\n\n if (to !== undefined) {\n end = clamp(to, length);\n }\n\n if (begin > end) {\n return new ArrayBuffer(0);\n }\n\n num = end - begin;\n target = new ArrayBuffer(num);\n targetArray = new Uint8Array(target);\n\n sourceArray = new Uint8Array(this, begin, num);\n targetArray.set(sourceArray);\n\n return target;\n };\n })();\n }\n\n // ---------------------------------------------------\n\n /**\n * Helpers.\n */\n\n function toUtf8(str) {\n if (/[\\u0080-\\uFFFF]/.test(str)) {\n str = unescape(encodeURIComponent(str));\n }\n\n return str;\n }\n\n function utf8Str2ArrayBuffer(str, returnUInt8Array) {\n var length = str.length,\n buff = new ArrayBuffer(length),\n arr = new Uint8Array(buff),\n i;\n\n for (i = 0; i < length; i += 1) {\n arr[i] = str.charCodeAt(i);\n }\n\n return returnUInt8Array ? arr : buff;\n }\n\n function arrayBuffer2Utf8Str(buff) {\n return String.fromCharCode.apply(null, new Uint8Array(buff));\n }\n\n function concatenateArrayBuffers(first, second, returnUInt8Array) {\n var result = new Uint8Array(first.byteLength + second.byteLength);\n\n result.set(new Uint8Array(first));\n result.set(new Uint8Array(second), first.byteLength);\n\n return returnUInt8Array ? result : result.buffer;\n }\n\n function hexToBinaryString(hex) {\n var bytes = [],\n length = hex.length,\n x;\n\n for (x = 0; x < length - 1; x += 2) {\n bytes.push(parseInt(hex.substr(x, 2), 16));\n }\n\n return String.fromCharCode.apply(String, bytes);\n }\n\n // ---------------------------------------------------\n\n /**\n * SparkMD5 OOP implementation.\n *\n * Use this class to perform an incremental md5, otherwise use the\n * static methods instead.\n */\n\n function SparkMD5() {\n // call reset to init the instance\n this.reset();\n }\n\n /**\n * Appends a string.\n * A conversion will be applied if an utf8 string is detected.\n *\n * @param {String} str The string to be appended\n *\n * @return {SparkMD5} The instance itself\n */\n SparkMD5.prototype.append = function (str) {\n // Converts the string to utf8 bytes if necessary\n // Then append as binary\n this.appendBinary(toUtf8(str));\n\n return this;\n };\n\n /**\n * Appends a binary string.\n *\n * @param {String} contents The binary string to be appended\n *\n * @return {SparkMD5} The instance itself\n */\n SparkMD5.prototype.appendBinary = function (contents) {\n this._buff += contents;\n this._length += contents.length;\n\n var length = this._buff.length,\n i;\n\n for (i = 64; i <= length; i += 64) {\n md5cycle(this._hash, md5blk(this._buff.substring(i - 64, i)));\n }\n\n this._buff = this._buff.substring(i - 64);\n\n return this;\n };\n\n /**\n * Finishes the incremental computation, reseting the internal state and\n * returning the result.\n *\n * @param {Boolean} raw True to get the raw string, false to get the hex string\n *\n * @return {String} The result\n */\n SparkMD5.prototype.end = function (raw) {\n var buff = this._buff,\n length = buff.length,\n i,\n tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n ret;\n\n for (i = 0; i < length; i += 1) {\n tail[i >> 2] |= buff.charCodeAt(i) << ((i % 4) << 3);\n }\n\n this._finish(tail, length);\n ret = hex(this._hash);\n\n if (raw) {\n ret = hexToBinaryString(ret);\n }\n\n this.reset();\n\n return ret;\n };\n\n /**\n * Resets the internal state of the computation.\n *\n * @return {SparkMD5} The instance itself\n */\n SparkMD5.prototype.reset = function () {\n this._buff = '';\n this._length = 0;\n this._hash = [1732584193, -271733879, -1732584194, 271733878];\n\n return this;\n };\n\n /**\n * Gets the internal state of the computation.\n *\n * @return {Object} The state\n */\n SparkMD5.prototype.getState = function () {\n return {\n buff: this._buff,\n length: this._length,\n hash: this._hash.slice()\n };\n };\n\n /**\n * Gets the internal state of the computation.\n *\n * @param {Object} state The state\n *\n * @return {SparkMD5} The instance itself\n */\n SparkMD5.prototype.setState = function (state) {\n this._buff = state.buff;\n this._length = state.length;\n this._hash = state.hash;\n\n return this;\n };\n\n /**\n * Releases memory used by the incremental buffer and other additional\n * resources. If you plan to use the instance again, use reset instead.\n */\n SparkMD5.prototype.destroy = function () {\n delete this._hash;\n delete this._buff;\n delete this._length;\n };\n\n /**\n * Finish the final calculation based on the tail.\n *\n * @param {Array} tail The tail (will be modified)\n * @param {Number} length The length of the remaining buffer\n */\n SparkMD5.prototype._finish = function (tail, length) {\n var i = length,\n tmp,\n lo,\n hi;\n\n tail[i >> 2] |= 0x80 << ((i % 4) << 3);\n if (i > 55) {\n md5cycle(this._hash, tail);\n for (i = 0; i < 16; i += 1) {\n tail[i] = 0;\n }\n }\n\n // Do the final computation based on the tail and length\n // Beware that the final length may not fit in 32 bits so we take care of that\n tmp = this._length * 8;\n tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/);\n lo = parseInt(tmp[2], 16);\n hi = parseInt(tmp[1], 16) || 0;\n\n tail[14] = lo;\n tail[15] = hi;\n md5cycle(this._hash, tail);\n };\n\n /**\n * Performs the md5 hash on a string.\n * A conversion will be applied if utf8 string is detected.\n *\n * @param {String} str The string\n * @param {Boolean} [raw] True to get the raw string, false to get the hex string\n *\n * @return {String} The result\n */\n SparkMD5.hash = function (str, raw) {\n // Converts the string to utf8 bytes if necessary\n // Then compute it using the binary function\n return SparkMD5.hashBinary(toUtf8(str), raw);\n };\n\n /**\n * Performs the md5 hash on a binary string.\n *\n * @param {String} content The binary string\n * @param {Boolean} [raw] True to get the raw string, false to get the hex string\n *\n * @return {String} The result\n */\n SparkMD5.hashBinary = function (content, raw) {\n var hash = md51(content),\n ret = hex(hash);\n\n return raw ? hexToBinaryString(ret) : ret;\n };\n\n // ---------------------------------------------------\n\n /**\n * SparkMD5 OOP implementation for array buffers.\n *\n * Use this class to perform an incremental md5 ONLY for array buffers.\n */\n SparkMD5.ArrayBuffer = function () {\n // call reset to init the instance\n this.reset();\n };\n\n /**\n * Appends an array buffer.\n *\n * @param {ArrayBuffer} arr The array to be appended\n *\n * @return {SparkMD5.ArrayBuffer} The instance itself\n */\n SparkMD5.ArrayBuffer.prototype.append = function (arr) {\n var buff = concatenateArrayBuffers(this._buff.buffer, arr, true),\n length = buff.length,\n i;\n\n this._length += arr.byteLength;\n\n for (i = 64; i <= length; i += 64) {\n md5cycle(this._hash, md5blk_array(buff.subarray(i - 64, i)));\n }\n\n this._buff = (i - 64) < length ? new Uint8Array(buff.buffer.slice(i - 64)) : new Uint8Array(0);\n\n return this;\n };\n\n /**\n * Finishes the incremental computation, reseting the internal state and\n * returning the result.\n *\n * @param {Boolean} raw True to get the raw string, false to get the hex string\n *\n * @return {String} The result\n */\n SparkMD5.ArrayBuffer.prototype.end = function (raw) {\n var buff = this._buff,\n length = buff.length,\n tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n i,\n ret;\n\n for (i = 0; i < length; i += 1) {\n tail[i >> 2] |= buff[i] << ((i % 4) << 3);\n }\n\n this._finish(tail, length);\n ret = hex(this._hash);\n\n if (raw) {\n ret = hexToBinaryString(ret);\n }\n\n this.reset();\n\n return ret;\n };\n\n /**\n * Resets the internal state of the computation.\n *\n * @return {SparkMD5.ArrayBuffer} The instance itself\n */\n SparkMD5.ArrayBuffer.prototype.reset = function () {\n this._buff = new Uint8Array(0);\n this._length = 0;\n this._hash = [1732584193, -271733879, -1732584194, 271733878];\n\n return this;\n };\n\n /**\n * Gets the internal state of the computation.\n *\n * @return {Object} The state\n */\n SparkMD5.ArrayBuffer.prototype.getState = function () {\n var state = SparkMD5.prototype.getState.call(this);\n\n // Convert buffer to a string\n state.buff = arrayBuffer2Utf8Str(state.buff);\n\n return state;\n };\n\n /**\n * Gets the internal state of the computation.\n *\n * @param {Object} state The state\n *\n * @return {SparkMD5.ArrayBuffer} The instance itself\n */\n SparkMD5.ArrayBuffer.prototype.setState = function (state) {\n // Convert string to buffer\n state.buff = utf8Str2ArrayBuffer(state.buff, true);\n\n return SparkMD5.prototype.setState.call(this, state);\n };\n\n SparkMD5.ArrayBuffer.prototype.destroy = SparkMD5.prototype.destroy;\n\n SparkMD5.ArrayBuffer.prototype._finish = SparkMD5.prototype._finish;\n\n /**\n * Performs the md5 hash on an array buffer.\n *\n * @param {ArrayBuffer} arr The array buffer\n * @param {Boolean} [raw] True to get the raw string, false to get the hex one\n *\n * @return {String} The result\n */\n SparkMD5.ArrayBuffer.hash = function (arr, raw) {\n var hash = md51_array(new Uint8Array(arr)),\n ret = hex(hash);\n\n return raw ? hexToBinaryString(ret) : ret;\n };\n\n return SparkMD5;\n}));\n","/**\n * File cache manager using Cache API and IndexedDB\n */\n\nimport SparkMD5 from 'spark-md5';\nimport { config } from '@xiboplayer/utils';\n\nconst CACHE_NAME = 'xibo-media-v1';\nconst DB_NAME = 'xibo-player';\nconst DB_VERSION = 1;\nconst STORE_FILES = 'files';\nconst CONCURRENT_CHUNKS = 4; // Download 4 chunks simultaneously for 4x speedup\n\n// Dynamic base path for multi-variant deployment (pwa, pwa-xmds, pwa-xlr)\nconst BASE = (typeof window !== 'undefined')\n ? window.location.pathname.replace(/\\/[^/]*$/, '').replace(/\\/$/, '') || '/player/pwa'\n : '/player/pwa';\n\nexport class CacheManager {\n constructor() {\n this.cache = null;\n this.db = null;\n // Dependants: mediaId → Set<layoutId> — tracks which layouts use each media file\n this.dependants = new Map();\n }\n\n /**\n * Extract filename from download URL\n * URL format: https://.../xmds.php?file=1.png&...\n */\n extractFilename(url) {\n try {\n const urlObj = new URL(url);\n const fileParam = urlObj.searchParams.get('file');\n return fileParam || 'unknown';\n } catch (e) {\n return 'unknown';\n }\n }\n\n /**\n * Rewrite CMS URL to use configured CMS address\n * Handles cases where RequiredFiles returns absolute URLs\n */\n rewriteUrl(url) {\n if (!url) return url;\n\n // If URL is absolute and points to a different domain, rewrite it\n try {\n const urlObj = new URL(url);\n const configUrl = new URL(config.cmsAddress);\n\n // If domains differ, replace with configured CMS address\n if (urlObj.origin !== configUrl.origin) {\n console.log(`[Cache] Rewriting URL: ${urlObj.origin} → ${configUrl.origin}`);\n urlObj.protocol = configUrl.protocol;\n urlObj.hostname = configUrl.hostname;\n urlObj.port = configUrl.port;\n return urlObj.toString();\n }\n } catch (e) {\n // Not a valid URL, return as-is\n }\n\n return url;\n }\n\n /**\n * Initialize cache and database\n */\n async init() {\n this.cache = await caches.open(CACHE_NAME);\n this.db = await this.openDB();\n }\n\n /**\n * Open IndexedDB\n */\n openDB() {\n return new Promise((resolve, reject) => {\n const request = indexedDB.open(DB_NAME, DB_VERSION);\n\n request.onerror = () => reject(request.error);\n request.onsuccess = () => resolve(request.result);\n\n request.onupgradeneeded = (event) => {\n const db = event.target.result;\n if (!db.objectStoreNames.contains(STORE_FILES)) {\n const store = db.createObjectStore(STORE_FILES, { keyPath: 'id' });\n store.createIndex('type', 'type', { unique: false });\n }\n };\n });\n }\n\n /**\n * Get file record from IndexedDB\n */\n async getFile(id) {\n return new Promise((resolve, reject) => {\n const tx = this.db.transaction(STORE_FILES, 'readonly');\n const store = tx.objectStore(STORE_FILES);\n const request = store.get(id);\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error);\n });\n }\n\n /**\n * Save file record to IndexedDB\n */\n async saveFile(fileRecord) {\n return new Promise((resolve, reject) => {\n const tx = this.db.transaction(STORE_FILES, 'readwrite');\n const store = tx.objectStore(STORE_FILES);\n const request = store.put(fileRecord);\n request.onsuccess = () => resolve();\n request.onerror = () => reject(request.error);\n });\n }\n\n /**\n * Get all file records\n */\n async getAllFiles() {\n return new Promise((resolve, reject) => {\n const tx = this.db.transaction(STORE_FILES, 'readonly');\n const store = tx.objectStore(STORE_FILES);\n const request = store.getAll();\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error);\n });\n }\n\n /**\n * Download and cache a file with MD5 verification\n * Handles large files with streaming to avoid memory issues\n *\n * Note: This method is a fallback for when Service Worker is not active.\n * When Service Worker is running, file downloads are handled by sw.js.\n */\n async downloadFile(fileInfo) {\n const { id, type, path, md5, download } = fileInfo;\n\n // Check if Service Worker is handling downloads\n if (typeof navigator !== 'undefined' && navigator.serviceWorker?.controller) {\n console.log(`[Cache] Service Worker active - skipping direct download for ${type}/${id}`);\n console.log(`[Cache] File will be downloaded by Service Worker in background`);\n return {\n id,\n type,\n path,\n md5: md5 || 'pending',\n size: 0,\n cachedAt: Date.now(),\n isServiceWorkerDownload: true\n };\n }\n\n // Skip files with no URL (widgets/resources generated on-demand)\n if (!path || path === 'null' || path === 'undefined') {\n console.log(`[Cache] Skipping ${type}/${id} - no download URL (will be generated on-demand)`);\n return null;\n }\n\n // Check if already cached\n const existing = await this.getFile(id);\n const cacheKey = this.getCacheKey(type, id);\n\n if (existing) {\n // Check if MD5 matches current expected value\n if (existing.md5 === md5) {\n // MD5 matches - verify file isn't corrupted\n const cachedResponse = await this.cache.match(cacheKey);\n\n if (cachedResponse && type === 'media') {\n const blob = await cachedResponse.blob();\n const contentType = cachedResponse.headers.get('Content-Type');\n\n // Delete bad cache (text/plain errors or tiny files)\n if (contentType === 'text/plain' || blob.size < 100) {\n console.warn(`[Cache] Bad cache detected for ${type}/${id} (${contentType}, ${blob.size} bytes) - re-downloading`);\n await this.cache.delete(cacheKey);\n // Continue to download below\n } else {\n console.log(`[Cache] ${type}/${id} already cached`);\n return existing;\n }\n } else {\n console.log(`[Cache] ${type}/${id} already cached`);\n return existing;\n }\n } else {\n // MD5 mismatch - file has been updated on CMS\n console.warn(`[Cache] ${type}/${id} MD5 changed (cached: ${existing.md5}, expected: ${md5}) - re-downloading`);\n await this.cache.delete(cacheKey);\n // Continue to download below\n }\n }\n\n console.log(`[Cache] Downloading ${type}/${id} from ${path}`);\n\n // Rewrite URL to use configured CMS (handles proxy case)\n const downloadUrl = this.rewriteUrl(path);\n console.log(`[Cache] Using URL: ${downloadUrl}`);\n\n // Check file size with HEAD request first (avoid downloading unnecessarily)\n const headResponse = await fetch(downloadUrl, { method: 'HEAD' });\n\n // HTTP 202 means Service Worker is still downloading in background\n // Don't proceed with caching - file isn't ready yet\n // Return pending metadata instead of throwing (allows collection to continue)\n if (headResponse.status === 202) {\n console.warn(`[Cache] ${type}/${id} still downloading in background (HTTP 202) - will retry on next collection`);\n return {\n id,\n type,\n path,\n md5: md5 || 'pending',\n size: 0,\n cachedAt: Date.now(),\n isPending: true // Mark as pending for retry\n };\n }\n\n const contentLength = parseInt(headResponse.headers.get('Content-Length') || '0');\n const isLargeFile = contentLength > 100 * 1024 * 1024; // > 100 MB\n\n console.log(`[Cache] File size: ${(contentLength / 1024 / 1024).toFixed(1)} MB ${isLargeFile ? '(large file)' : ''}`);\n\n // filename already has cacheKey from above (line 143)\n const filename = type === 'media' ? this.extractFilename(path) : id;\n\n // Also create MD5-based cache key for content-addressable lookup\n // This allows Service Worker to find files by content hash instead of filename\n const md5CacheKey = md5 ? `/cache/hash/${md5}` : null;\n\n let calculatedMd5;\n let fileSize;\n\n if (isLargeFile) {\n // Large file: Cache in background for future use, but don't block\n console.log(`[Cache] Large file detected (${(contentLength / 1024 / 1024).toFixed(1)} MB), caching in background`);\n\n // Start background download (don't await)\n this.downloadLargeFileInBackground(downloadUrl, cacheKey, contentLength, filename, id, type, path, md5)\n .catch(err => console.warn(`[Cache] Background download failed for ${id}:`, err));\n\n // Return immediately - don't block collection cycle\n const metadata = {\n id,\n type,\n path,\n md5: md5 || 'pending',\n size: contentLength,\n cachedAt: Date.now(),\n isBackgroundDownload: true\n };\n\n await this.saveFile(metadata);\n\n console.log(`[Cache] ${type}/${id} downloading in background (${contentLength} bytes)`);\n\n return metadata;\n } else {\n // Small file: Download fully and verify MD5\n this.notifyDownloadProgress(filename, 0, contentLength);\n\n // Now do the actual download for small files\n const response = await fetch(downloadUrl);\n\n // HTTP 202 means Service Worker is still downloading in background\n // Don't cache the 202 response - it's just a placeholder message\n // Return pending metadata instead of throwing (allows collection to continue)\n if (response.status === 202) {\n console.warn(`[Cache] ${type}/${id} still downloading in background (HTTP 202) - will retry on next collection`);\n return {\n id,\n type,\n path,\n md5: md5 || 'pending',\n size: 0,\n cachedAt: Date.now(),\n isPending: true // Mark as pending for retry\n };\n }\n\n if (!response.ok) {\n throw new Error(`Failed to download ${path}: ${response.status}`);\n }\n\n const blob = await response.blob();\n const arrayBuffer = await blob.arrayBuffer();\n\n // Verify MD5\n calculatedMd5 = SparkMD5.ArrayBuffer.hash(arrayBuffer);\n if (md5 && calculatedMd5 !== md5) {\n // KIOSK MODE: Log MD5 mismatches but always continue\n // Rendering methods (renderImage, renderVideo, renderLayout, etc.) will\n // naturally fail if wrong file type is provided\n // This ensures maximum uptime for kiosk deployments\n console.warn(`[Cache] MD5 mismatch for ${type}/${id}:`);\n console.warn(`[Cache] Expected: ${md5}`);\n console.warn(`[Cache] Got: ${calculatedMd5}`);\n console.warn(`[Cache] Accepting file anyway (kiosk mode - renderer will validate)`);\n\n // Use the file regardless - let the renderer handle validation\n calculatedMd5 = md5; // Prevent re-download loop\n }\n\n // Cache the response\n await this.cache.put(cacheKey, new Response(blob, {\n headers: {\n 'Content-Type': response.headers.get('Content-Type') || 'application/octet-stream',\n 'Content-Length': blob.size\n }\n }));\n\n fileSize = blob.size;\n this.notifyDownloadProgress(filename, fileSize, contentLength, true);\n console.log(`[Cache] Cached ${type}/${id} (${fileSize} bytes, MD5: ${calculatedMd5})`);\n }\n\n // Save metadata\n const fileRecord = {\n id,\n type,\n path,\n md5: calculatedMd5,\n size: fileSize,\n cachedAt: Date.now()\n };\n await this.saveFile(fileRecord);\n\n return fileRecord;\n }\n\n /**\n * Get cache key for a file\n * For media, uses the actual filename; for layouts, uses the ID\n */\n getCacheKey(type, id, filename = null) {\n const key = filename || id;\n return `${BASE}/cache/${type}/${key}`;\n }\n\n /**\n * Get cached file as blob\n */\n async getCachedFile(type, id) {\n const cacheKey = this.getCacheKey(type, id);\n const response = await this.cache.match(cacheKey);\n\n if (!response) {\n return null;\n }\n return await response.blob();\n }\n\n /**\n * Get cached file as Response (preserves headers like Content-Type)\n */\n async getCachedResponse(type, id) {\n const cacheKey = this.getCacheKey(type, id);\n return await this.cache.match(cacheKey);\n }\n\n /**\n * Get cached file as text\n */\n async getCachedFileText(type, id) {\n const cacheKey = this.getCacheKey(type, id);\n const response = await this.cache.match(cacheKey);\n if (!response) {\n return null;\n }\n return await response.text();\n }\n\n /**\n * Store widget HTML in cache for iframe loading\n * @param {string} layoutId - Layout ID\n * @param {string} regionId - Region ID\n * @param {string} mediaId - Media ID\n * @param {string} html - Widget HTML content\n * @returns {Promise<string>} Cache key URL\n */\n async cacheWidgetHtml(layoutId, regionId, mediaId, html) {\n const cacheKey = `${BASE}/cache/widget/${layoutId}/${regionId}/${mediaId}`;\n const cache = await caches.open(CACHE_NAME);\n\n // Inject <base> tag to fix relative paths for widget dependencies\n // Widget HTML has relative paths like \"bundle.min.js\" that should resolve to /player/cache/media/\n const baseTag = '<base href=\"/player/cache/media/\">';\n let modifiedHtml = html;\n\n // Insert base tag after <head> opening tag\n if (html.includes('<head>')) {\n modifiedHtml = html.replace('<head>', '<head>' + baseTag);\n } else if (html.includes('<HEAD>')) {\n modifiedHtml = html.replace('<HEAD>', '<HEAD>' + baseTag);\n } else {\n // No head tag, prepend base tag\n modifiedHtml = baseTag + html;\n }\n\n // Rewrite absolute CMS signed URLs to local cache paths\n // Matches: https://cms/xmds.php?file=bundle.min.js&...&X-Amz-Signature=...\n // These absolute URLs bypass the <base> tag entirely, causing slow CMS fetches\n const cmsUrlRegex = /https?:\\/\\/[^\"'\\s)]+xmds\\.php\\?[^\"'\\s)]*file=([^&\"'\\s)]+)[^\"'\\s)]*/g;\n const staticResources = [];\n modifiedHtml = modifiedHtml.replace(cmsUrlRegex, (match, filename) => {\n const localPath = `${BASE}/cache/static/${filename}`;\n staticResources.push({ filename, originalUrl: match });\n console.log(`[Cache] Rewrote widget URL: ${filename} → ${localPath}`);\n return localPath;\n });\n\n // Inject CSS default for object-position to suppress CMS template warning\n // CMS global-elements.xml uses {{alignId}} {{valignId}} which produces\n // invalid CSS (empty value) when alignment is not configured\n const cssFixTag = '<style>img,video{object-position:center center}</style>';\n if (modifiedHtml.includes('</head>')) {\n modifiedHtml = modifiedHtml.replace('</head>', cssFixTag + '</head>');\n } else if (modifiedHtml.includes('</HEAD>')) {\n modifiedHtml = modifiedHtml.replace('</HEAD>', cssFixTag + '</HEAD>');\n }\n\n // Rewrite Interactive Control hostAddress to SW-interceptable path\n // The IC library uses hostAddress + '/info', '/trigger', etc.\n // Original: hostAddress: \"https://cms.example.com\" → XHR to /info goes to CMS (fails)\n // Rewritten: hostAddress: \"/player/pwa/ic\" → XHR to /player/pwa/ic/info (intercepted by SW)\n modifiedHtml = modifiedHtml.replace(\n /hostAddress\\s*:\\s*[\"']https?:\\/\\/[^\"']+[\"']/g,\n `hostAddress: \"${BASE}/ic\"`\n );\n\n console.log(`[Cache] Injected base tag and rewrote CMS URLs in widget HTML`);\n\n // Construct full URL for cache storage\n const cacheUrl = new URL(cacheKey, window.location.origin);\n\n const response = new Response(modifiedHtml, {\n headers: {\n 'Content-Type': 'text/html; charset=utf-8',\n 'Access-Control-Allow-Origin': '*'\n }\n });\n\n await cache.put(cacheUrl, response);\n console.log(`[Cache] Stored widget HTML at ${cacheKey} (${modifiedHtml.length} bytes)`);\n\n // Fetch and cache static resources (shared Cache API - accessible from main thread and SW)\n if (staticResources.length > 0) {\n const STATIC_CACHE_NAME = 'xibo-static-v1';\n const staticCache = await caches.open(STATIC_CACHE_NAME);\n\n await Promise.all(staticResources.map(async ({ filename, originalUrl }) => {\n const staticKey = `${BASE}/cache/static/${filename}`;\n const existing = await staticCache.match(staticKey);\n if (existing) return; // Already cached\n\n try {\n const resp = await fetch(originalUrl);\n if (!resp.ok) {\n console.warn(`[Cache] Failed to fetch static resource: ${filename} (HTTP ${resp.status})`);\n return;\n }\n\n const ext = filename.split('.').pop().toLowerCase();\n const contentType = {\n 'js': 'application/javascript',\n 'css': 'text/css',\n 'otf': 'font/otf', 'ttf': 'font/ttf',\n 'woff': 'font/woff', 'woff2': 'font/woff2',\n 'eot': 'application/vnd.ms-fontobject',\n 'svg': 'image/svg+xml'\n }[ext] || 'application/octet-stream';\n\n // For CSS files, rewrite font URLs and cache referenced font files\n if (ext === 'css') {\n let cssText = await resp.text();\n const fontResources = [];\n const fontUrlRegex = /url\\((['\"]?)(https?:\\/\\/[^'\")\\s]+\\?[^'\")\\s]*file=([^&'\")\\s]+\\.(?:woff2?|ttf|otf|eot|svg))[^'\")\\s]*)\\1\\)/gi;\n cssText = cssText.replace(fontUrlRegex, (_match, quote, fullUrl, fontFilename) => {\n fontResources.push({ filename: fontFilename, originalUrl: fullUrl });\n console.log(`[Cache] Rewrote font URL in CSS: ${fontFilename}`);\n return `url(${quote}${BASE}/cache/static/${encodeURIComponent(fontFilename)}${quote})`;\n });\n\n await staticCache.put(staticKey, new Response(cssText, {\n headers: { 'Content-Type': 'text/css' }\n }));\n console.log(`[Cache] Cached CSS with ${fontResources.length} rewritten font URLs: ${filename}`);\n\n // Fetch and cache referenced font files\n await Promise.all(fontResources.map(async ({ filename: fontFile, originalUrl: fontUrl }) => {\n const fontKey = `${BASE}/cache/static/${encodeURIComponent(fontFile)}`;\n const existingFont = await staticCache.match(fontKey);\n if (existingFont) return;\n\n try {\n const fontResp = await fetch(fontUrl);\n if (!fontResp.ok) {\n console.warn(`[Cache] Failed to fetch font: ${fontFile} (HTTP ${fontResp.status})`);\n return;\n }\n const fontBlob = await fontResp.blob();\n const fontExt = fontFile.split('.').pop().toLowerCase();\n const fontContentType = {\n 'otf': 'font/otf', 'ttf': 'font/ttf',\n 'woff': 'font/woff', 'woff2': 'font/woff2',\n 'eot': 'application/vnd.ms-fontobject',\n 'svg': 'image/svg+xml'\n }[fontExt] || 'application/octet-stream';\n\n await staticCache.put(fontKey, new Response(fontBlob, {\n headers: { 'Content-Type': fontContentType }\n }));\n console.log(`[Cache] Cached font: ${fontFile} (${fontContentType}, ${fontBlob.size} bytes)`);\n } catch (fontErr) {\n console.warn(`[Cache] Failed to cache font: ${fontFile}`, fontErr);\n }\n }));\n } else {\n const blob = await resp.blob();\n await staticCache.put(staticKey, new Response(blob, {\n headers: { 'Content-Type': contentType }\n }));\n console.log(`[Cache] Cached static resource: ${filename} (${contentType}, ${blob.size} bytes)`);\n }\n } catch (error) {\n console.warn(`[Cache] Failed to cache static resource: ${filename}`, error);\n }\n }));\n }\n\n return cacheKey;\n }\n\n /**\n * Track that a media file is used by a layout (dependant)\n * @param {string|number} mediaId\n * @param {string|number} layoutId\n */\n addDependant(mediaId, layoutId) {\n const key = String(mediaId);\n if (!this.dependants.has(key)) {\n this.dependants.set(key, new Set());\n }\n this.dependants.get(key).add(String(layoutId));\n }\n\n /**\n * Remove a layout from all dependant sets (layout removed from schedule)\n * @param {string|number} layoutId\n * @returns {string[]} Media IDs that are now orphaned (no layouts reference them)\n */\n removeLayoutDependants(layoutId) {\n const lid = String(layoutId);\n const orphaned = [];\n\n for (const [mediaId, layouts] of this.dependants) {\n layouts.delete(lid);\n if (layouts.size === 0) {\n this.dependants.delete(mediaId);\n orphaned.push(mediaId);\n }\n }\n\n if (orphaned.length > 0) {\n console.log(`[Cache] ${orphaned.length} media files orphaned after layout ${layoutId} removed:`, orphaned);\n }\n return orphaned;\n }\n\n /**\n * Check if a media file is still referenced by any layout\n * @param {string|number} mediaId\n * @returns {boolean}\n */\n isMediaReferenced(mediaId) {\n const layouts = this.dependants.get(String(mediaId));\n return layouts ? layouts.size > 0 : false;\n }\n\n /**\n * Download large file in background (non-blocking)\n * Continues after collection cycle completes\n * Uses parallel chunk downloads for 4x speedup\n */\n async downloadLargeFileInBackground(downloadUrl, cacheKey, contentLength, filename, id, type, path, md5) {\n const CHUNK_SIZE = 50 * 1024 * 1024; // 50 MB chunks\n let downloadedBytes = 0;\n\n console.log(`[Cache] Background download started: ${filename}`);\n this.notifyDownloadProgress(filename, 0, contentLength);\n\n try {\n // Calculate all chunk ranges\n const chunkRanges = [];\n for (let start = 0; start < contentLength; start += CHUNK_SIZE) {\n const end = Math.min(start + CHUNK_SIZE - 1, contentLength - 1);\n chunkRanges.push({ start, end, index: chunkRanges.length });\n }\n\n console.log(`[Cache] Downloading ${chunkRanges.length} chunks in parallel (${CONCURRENT_CHUNKS} concurrent)`);\n\n // Parallel download with concurrency limit\n const chunkMap = new Map(); // position -> blob\n let nextChunkIndex = 0;\n\n const downloadChunk = async (range) => {\n const rangeHeader = `bytes=${range.start}-${range.end}`;\n\n try {\n const chunkResponse = await fetch(downloadUrl, {\n headers: { 'Range': rangeHeader }\n });\n\n if (!chunkResponse.ok && chunkResponse.status !== 206) {\n throw new Error(`Chunk ${range.index} failed: ${chunkResponse.status}`);\n }\n\n const chunkBlob = await chunkResponse.blob();\n chunkMap.set(range.index, chunkBlob);\n\n downloadedBytes += chunkBlob.size;\n const progress = ((downloadedBytes / contentLength) * 100).toFixed(1);\n console.log(`[Cache] Chunk ${range.index}/${chunkRanges.length - 1} complete (${progress}%)`);\n this.notifyDownloadProgress(filename, downloadedBytes, contentLength);\n\n return chunkBlob;\n } catch (error) {\n console.error(`[Cache] Chunk ${range.index} failed:`, error);\n throw error;\n }\n };\n\n // Download with concurrency control\n const downloadNext = async () => {\n while (nextChunkIndex < chunkRanges.length) {\n const range = chunkRanges[nextChunkIndex++];\n await downloadChunk(range);\n }\n };\n\n // Start CONCURRENT_CHUNKS parallel downloaders\n const downloaders = [];\n for (let i = 0; i < CONCURRENT_CHUNKS; i++) {\n downloaders.push(downloadNext());\n }\n\n await Promise.all(downloaders);\n\n // Reassemble chunks in order\n const orderedChunks = [];\n for (let i = 0; i < chunkRanges.length; i++) {\n orderedChunks.push(chunkMap.get(i));\n }\n\n // Combine all chunks\n const blob = new Blob(orderedChunks);\n\n // Get content type from first chunk response\n const contentType = orderedChunks[0]?.type || 'video/mp4';\n\n // Cache the complete file\n await this.cache.put(cacheKey, new Response(blob, {\n headers: {\n 'Content-Type': contentType,\n 'Content-Length': blob.size,\n 'Accept-Ranges': 'bytes'\n }\n }));\n\n // Update metadata\n const metadata = {\n id,\n type,\n path,\n md5: md5 || 'background',\n size: blob.size,\n cachedAt: Date.now(),\n isBackgroundDownload: false,\n cached: true\n };\n\n await this.saveFile(metadata);\n\n this.notifyDownloadProgress(filename, downloadedBytes, contentLength, true);\n\n console.log(`[Cache] Background download complete: ${filename} (${blob.size} bytes in ${orderedChunks.length} chunks)`);\n\n // Notify that file is now available for playback\n window.dispatchEvent(new CustomEvent('media-cached', {\n detail: { filename, id, type, size: blob.size }\n }));\n } catch (error) {\n console.error(`[Cache] Background download failed for ${filename}:`, error);\n this.notifyDownloadProgress(filename, downloadedBytes, contentLength, false, true);\n }\n }\n\n /**\n * Notify UI about download progress\n */\n notifyDownloadProgress(filename, loaded, total, complete = false, error = false) {\n const event = new CustomEvent('download-progress', {\n detail: {\n filename,\n loaded,\n total,\n percent: total > 0 ? (loaded / total) * 100 : 0,\n complete,\n error\n }\n });\n window.dispatchEvent(event);\n }\n\n /**\n * Clear all cached files\n */\n async clearAll() {\n await caches.delete(CACHE_NAME);\n this.cache = await caches.open(CACHE_NAME);\n\n return new Promise((resolve, reject) => {\n const tx = this.db.transaction(STORE_FILES, 'readwrite');\n const store = tx.objectStore(STORE_FILES);\n const request = store.clear();\n request.onsuccess = () => resolve();\n request.onerror = () => reject(request.error);\n });\n }\n}\n\nexport const cacheManager = new CacheManager();\n","/**\n * CacheProxy - Service Worker cache interface\n *\n * Provides a proxy to Service Worker for background downloads and caching.\n * Service Worker MUST be available - no fallback (committed to SW architecture).\n *\n * Architecture:\n * ┌─────────────────────────────────────────┐\n * │ CacheProxy (Service Worker Only) │\n * │ - Waits for Service Worker ready │\n * │ - Routes all requests to SW │\n * │ - No fallback to direct cache │\n * └─────────────────────────────────────────┘\n * ↓\n * ┌──────────────────┐\n * │ ServiceWorker │\n * │ Backend │\n * │ - postMessage │\n * │ - Background DL │\n * └──────────────────┘\n *\n * Usage:\n * const proxy = new CacheProxy();\n * await proxy.init(); // Waits for SW to be ready\n *\n * const blob = await proxy.getFile('media', '123');\n * await proxy.requestDownload([{ id, type, path, md5 }]);\n * const isCached = await proxy.isCached('layout', '456');\n */\n\nimport { EventEmitter, createLogger } from '@xiboplayer/utils';\n\nconst log = createLogger('CacheProxy');\n\n// Dynamic base path for multi-variant deployment (pwa, pwa-xmds, pwa-xlr)\nconst BASE = (typeof window !== 'undefined')\n ? window.location.pathname.replace(/\\/[^/]*$/, '').replace(/\\/$/, '') || '/player/pwa'\n : '/player/pwa';\n\n/**\n * ServiceWorkerBackend - Uses Service Worker for downloads and caching\n */\nclass ServiceWorkerBackend extends EventEmitter {\n constructor() {\n super();\n this.controller = null;\n this.fetchReady = false;\n this.fetchReadyPromise = null;\n this.fetchReadyResolve = null;\n }\n\n async init() {\n // Create promise for fetch readiness (resolved when SW sends SW_READY)\n this.fetchReadyPromise = new Promise(resolve => {\n this.fetchReadyResolve = resolve;\n });\n\n // Listen for SW_READY message\n navigator.serviceWorker.addEventListener('message', (event) => {\n if (event.data?.type === 'SW_READY') {\n log.info('Received SW_READY signal - fetch handler is ready');\n this.fetchReady = true;\n this.fetchReadyResolve();\n }\n });\n\n // Get the active Service Worker (don't require controller)\n if ('serviceWorker' in navigator) {\n const registration = await navigator.serviceWorker.getRegistration();\n\n // Use active SW if it exists (controller not required!)\n if (registration && registration.active && registration.active.state === 'activated') {\n log.info('Using active Service Worker (controller not required)');\n this.controller = navigator.serviceWorker.controller || registration.active;\n\n // Request readiness signal from SW\n this.controller.postMessage({ type: 'PING' });\n\n log.info('Service Worker backend initialized, waiting for fetch readiness...');\n return;\n }\n\n // Fall back to waiting for ready\n await navigator.serviceWorker.ready;\n this.controller = navigator.serviceWorker.controller;\n\n if (!this.controller) {\n throw new Error('Service Worker not controlling page');\n }\n\n // Request readiness signal from SW\n this.controller.postMessage({ type: 'PING' });\n\n log.info('Service Worker backend initialized, waiting for fetch readiness...');\n } else {\n throw new Error('Service Worker not supported');\n }\n }\n\n /**\n * Get file from cache (via Service Worker)\n * @param {string} type - 'media', 'layout', 'widget'\n * @param {string} id - File ID\n * @returns {Promise<Blob|null>}\n */\n async getFile(type, id) {\n // Wait for SW fetch handler to be ready (eliminates race condition)\n if (!this.fetchReady) {\n log.debug(`Waiting for SW fetch handler to be ready before fetching ${type}/${id}...`);\n await this.fetchReadyPromise;\n log.debug(`SW fetch handler ready, proceeding with fetch`);\n }\n\n // Service Worker serves files via fetch interception\n // Construct cache URL and fetch it\n const cacheUrl = `${BASE}/cache/${type}/${id}`;\n\n log.debug(`getFile(${type}, ${id}) → fetching ${cacheUrl}`);\n log.debug(`About to call fetch()...`);\n\n try {\n log.debug(`Calling fetch(${cacheUrl})...`);\n const response = await fetch(cacheUrl);\n log.debug(`fetch returned, status:`, response.status, response.statusText);\n\n if (!response.ok) {\n log.debug(`Response not OK (${response.status}), returning null`);\n if (response.status === 404) {\n return null; // Not cached\n }\n throw new Error(`Failed to get file: ${response.status}`);\n }\n\n log.debug(`Response OK, getting blob...`);\n const blob = await response.blob();\n log.debug(`Got blob, size:`, blob.size);\n return blob;\n } catch (error) {\n log.error('getFile EXCEPTION:', error);\n log.error('Error name:', error.name);\n log.error('Error message:', error.message);\n return null;\n }\n }\n\n /**\n * Check if file exists in cache (supports both whole files and chunked storage)\n * Service Worker's CacheManager.fileExists() handles the logic internally\n * @param {string} type - 'media', 'layout', 'widget'\n * @param {string} id - File ID\n * @returns {Promise<boolean>}\n */\n async hasFile(type, id) {\n // Wait for SW fetch handler to be ready\n if (!this.fetchReady) {\n await this.fetchReadyPromise;\n }\n\n const cacheUrl = `${BASE}/cache/${type}/${id}`;\n\n try {\n // SW's handleRequest uses CacheManager.fileExists() internally\n // Returns 200 for both whole files and chunked files (via metadata check)\n const response = await fetch(cacheUrl, { method: 'HEAD' });\n return response.ok;\n } catch (error) {\n return false;\n }\n }\n\n /**\n * Request downloads from Service Worker (non-blocking)\n * @param {Object|Array} payload - Either { layouts: [{ layoutId, mediaFiles }] } or flat Array of files\n * @returns {Promise<void>}\n */\n async requestDownload(payload) {\n if (!this.controller) {\n throw new Error('Service Worker not available');\n }\n\n // Support both grouped and flat payload (backward compat)\n const data = Array.isArray(payload)\n ? { files: payload }\n : payload;\n\n return new Promise((resolve, reject) => {\n const messageChannel = new MessageChannel();\n\n messageChannel.port1.onmessage = (event) => {\n const { success, error, enqueuedCount, activeCount, queuedCount } = event.data;\n if (success) {\n log.info('Download request acknowledged:', enqueuedCount, 'files');\n log.info('Queue state:', activeCount, 'active,', queuedCount, 'queued');\n resolve();\n } else {\n reject(new Error(error || 'Service Worker download failed'));\n }\n };\n\n this.controller.postMessage(\n {\n type: 'DOWNLOAD_FILES',\n data\n },\n [messageChannel.port2]\n );\n });\n }\n\n /**\n * Tell SW to prioritize downloading a specific file\n * Moves it to the front of the download queue if still queued\n * @param {string} fileType - 'media' or 'layout'\n * @param {string} fileId - File ID\n */\n async prioritizeDownload(fileType, fileId) {\n if (!this.controller) return;\n\n return new Promise((resolve) => {\n const messageChannel = new MessageChannel();\n messageChannel.port1.onmessage = (event) => resolve(event.data);\n this.controller.postMessage(\n { type: 'PRIORITIZE_DOWNLOAD', data: { fileType, fileId } },\n [messageChannel.port2]\n );\n });\n }\n\n /**\n * Check if file is cached (delegates to hasFile for consistent fetchReady handling)\n * @param {string} type - 'media', 'layout', 'widget'\n * @param {string} id - File ID\n * @returns {Promise<boolean>}\n */\n async isCached(type, id) {\n return this.hasFile(type, id);\n }\n\n /**\n * Prioritize layout files — reorder queue and hold other downloads until done.\n * @param {string[]} mediaIds - Media IDs needed by the current layout\n */\n async prioritizeLayoutFiles(mediaIds) {\n if (!this.controller) return;\n this.controller.postMessage({ type: 'PRIORITIZE_LAYOUT_FILES', data: { mediaIds } });\n }\n}\n\n// DirectCacheBackend removed - Service Worker only architecture\n\n/**\n * CacheProxy - Service Worker only interface\n */\nexport class CacheProxy extends EventEmitter {\n constructor() {\n super();\n this.backend = null;\n this.backendType = 'service-worker';\n }\n\n /**\n * Initialize proxy - WAITS for Service Worker to be ready\n */\n async init() {\n if (!('serviceWorker' in navigator)) {\n throw new Error('Service Worker not supported - PWA requires Service Worker');\n }\n\n log.debug('Checking Service Worker state...');\n log.debug('controller =', navigator.serviceWorker.controller);\n\n // Check if SW registration exists (better than checking controller)\n const registration = await navigator.serviceWorker.getRegistration();\n log.debug('registration =', registration);\n log.debug('active =', registration?.active);\n log.debug('installing =', registration?.installing);\n log.debug('waiting =', registration?.waiting);\n\n // FAST PATH: If active SW exists AND no new SW is installing, use it immediately\n if (registration && registration.active && !registration.installing && !registration.waiting) {\n log.info('Active Service Worker found (no updates pending)');\n log.debug('SW state =', registration.active.state);\n\n // If not controlling yet, give it a moment to claim page\n if (!navigator.serviceWorker.controller) {\n log.debug('Not controlling yet, waiting 200ms for claim...');\n await new Promise(resolve => setTimeout(resolve, 200));\n log.debug('After wait, controller =', navigator.serviceWorker.controller);\n }\n\n // Use the active SW (even if controller is still null - it will work)\n this.backend = new ServiceWorkerBackend();\n await this.backend.init();\n log.info('Service Worker backend ready (fast path)');\n return;\n }\n\n // If there's a new SW installing/waiting, wait for it instead of using old one\n if (registration && (registration.installing || registration.waiting)) {\n log.info('New Service Worker detected, waiting for it to activate...');\n log.debug('installing =', registration.installing?.state);\n log.debug('waiting =', registration.waiting?.state);\n }\n\n // SLOW PATH: No active SW, wait for registration (fresh install)\n log.info('No active Service Worker, waiting for registration...');\n\n // Wait with timeout\n const swReady = navigator.serviceWorker.ready;\n const timeout = new Promise((_, reject) =>\n setTimeout(() => reject(new Error('Service Worker ready timeout after 10s')), 10000)\n );\n\n try {\n await Promise.race([swReady, timeout]);\n log.debug('Service Worker ready promise resolved');\n } catch (error) {\n log.error('Service Worker wait failed:', error);\n throw new Error('Service Worker not ready - please reload page');\n }\n\n // Wait for SW to claim page\n await new Promise(resolve => setTimeout(resolve, 100));\n log.debug('After claim wait, controller =', navigator.serviceWorker.controller);\n\n // Controller not required - we can use registration.active instead\n // This handles the case where SW is active but hasn't set controller yet (timing issue)\n this.backend = new ServiceWorkerBackend();\n await this.backend.init();\n log.info('Service Worker backend ready (slow path)');\n }\n\n /**\n * Get file from cache\n * @param {string} type - 'media', 'layout', 'widget'\n * @param {string} id - File ID\n * @returns {Promise<Blob|null>}\n */\n async getFile(type, id) {\n if (!this.backend) {\n throw new Error('CacheProxy not initialized');\n }\n return await this.backend.getFile(type, id);\n }\n\n /**\n * Check if file exists in cache (for streaming - no blob creation)\n * @param {string} type - 'media', 'layout', 'widget'\n * @param {string} id - File ID\n * @returns {Promise<boolean>}\n */\n async hasFile(type, id) {\n if (!this.backend) {\n throw new Error('CacheProxy not initialized');\n }\n return await this.backend.hasFile(type, id);\n }\n\n /**\n * Request file downloads\n * Service Worker: Non-blocking (downloads in background)\n * Direct cache: Blocking (downloads sequentially)\n *\n * @param {Array} files - Array of { id, type, path, md5 }\n * @returns {Promise<void>}\n */\n async requestDownload(files) {\n if (!this.backend) {\n throw new Error('CacheProxy not initialized');\n }\n return await this.backend.requestDownload(files);\n }\n\n /**\n * Prioritize downloading a specific file (move to front of queue)\n * @param {string} fileType - 'media' or 'layout'\n * @param {string} fileId - File ID\n */\n async prioritizeDownload(fileType, fileId) {\n if (!this.backend?.prioritizeDownload) return;\n return await this.backend.prioritizeDownload(fileType, fileId);\n }\n\n /**\n * Check if file is cached (delegates to hasFile for consistent fetchReady handling)\n * @param {string} type - 'media', 'layout', 'widget'\n * @param {string} id - File ID\n * @returns {Promise<boolean>}\n */\n async isCached(type, id) {\n return this.hasFile(type, id);\n }\n\n /**\n * Prioritize layout files — reorder queue and hold other downloads until done.\n * @param {string[]} mediaIds - Media IDs needed by the current layout\n */\n async prioritizeLayoutFiles(mediaIds) {\n if (!this.backend?.prioritizeLayoutFiles) return;\n return await this.backend.prioritizeLayoutFiles(mediaIds);\n }\n\n /**\n * Get backend type for debugging\n * @returns {string} 'service-worker' or 'direct'\n */\n getBackendType() {\n return this.backendType;\n }\n\n /**\n * Check if Service Worker is being used\n * @returns {boolean}\n */\n isUsingServiceWorker() {\n return this.backendType === 'service-worker';\n }\n\n /**\n * Delete files from cache (purge obsolete media)\n * @param {Array<{type: string, id: string}>} files - Files to delete\n * @returns {Promise<{deleted: number, total: number}>}\n */\n async deleteFiles(files) {\n if (!this.backend) {\n throw new Error('CacheProxy not initialized');\n }\n\n return new Promise((resolve, reject) => {\n const channel = new MessageChannel();\n\n channel.port1.onmessage = (event) => {\n const { success, error, deleted, total } = event.data;\n if (success) {\n resolve({ deleted, total });\n } else {\n reject(new Error(error || 'Delete failed'));\n }\n };\n\n navigator.serviceWorker.controller.postMessage(\n { type: 'DELETE_FILES', data: { files } },\n [channel.port2]\n );\n\n setTimeout(() => resolve({ deleted: 0, total: files.length }), 5000);\n });\n }\n\n /**\n * Pre-warm video chunks into SW BlobCache for faster playback startup.\n * Loads first and last chunks into memory so moov atom probing is instant.\n * @param {number[]} mediaIds - Media file IDs to pre-warm\n * @returns {Promise<{warmed: number, total: number}>}\n */\n async prewarmVideoChunks(mediaIds) {\n if (!mediaIds || mediaIds.length === 0) return { warmed: 0, total: 0 };\n\n const controller = navigator.serviceWorker?.controller;\n if (!controller) return { warmed: 0, total: mediaIds.length };\n\n return new Promise((resolve) => {\n const channel = new MessageChannel();\n\n channel.port1.onmessage = (event) => {\n const { success, warmed, total } = event.data;\n resolve(success ? { warmed, total } : { warmed: 0, total: mediaIds.length });\n };\n\n controller.postMessage(\n { type: 'PREWARM_VIDEO_CHUNKS', data: { mediaIds } },\n [channel.port2]\n );\n\n // Don't block layout rendering for more than 2s\n setTimeout(() => resolve({ warmed: 0, total: mediaIds.length }), 2000);\n });\n }\n\n /**\n * Get download progress from Service Worker\n * @returns {Promise<Object>} Progress info for all active downloads\n */\n async getDownloadProgress() {\n if (!this.backend) {\n throw new Error('CacheProxy not initialized');\n }\n\n return new Promise((resolve) => {\n const channel = new MessageChannel();\n\n channel.port1.onmessage = (event) => {\n const { success, progress } = event.data;\n resolve(success ? progress : {});\n };\n\n navigator.serviceWorker.controller.postMessage(\n { type: 'GET_DOWNLOAD_PROGRESS' },\n [channel.port2]\n );\n\n // Timeout after 1 second\n setTimeout(() => resolve({}), 1000);\n });\n }\n}\n"],"names":["EventEmitter","event","callback","wrapper","args","listeners","index","listener","factory","module","undefined","hex_chr","md5cycle","x","k","a","b","c","d","md5blk","s","md5blks","i","md5blk_array","md51","n","state","length","tail","tmp","lo","hi","md51_array","rhex","j","hex","clamp","val","from","to","begin","end","num","target","targetArray","sourceArray","toUtf8","str","utf8Str2ArrayBuffer","returnUInt8Array","buff","arr","arrayBuffer2Utf8Str","concatenateArrayBuffers","first","second","result","hexToBinaryString","bytes","SparkMD5","contents","raw","ret","content","hash","CACHE_NAME","DB_NAME","DB_VERSION","STORE_FILES","CONCURRENT_CHUNKS","BASE","CacheManager","url","urlObj","configUrl","config","resolve","reject","request","db","id","fileRecord","fileInfo","type","path","md5","download","_a","existing","cacheKey","cachedResponse","blob","contentType","downloadUrl","headResponse","contentLength","isLargeFile","filename","calculatedMd5","fileSize","err","metadata","response","arrayBuffer","layoutId","regionId","mediaId","html","cache","baseTag","modifiedHtml","cmsUrlRegex","staticResources","match","localPath","cssFixTag","cacheUrl","staticCache","originalUrl","staticKey","resp","ext","cssText","fontResources","fontUrlRegex","_match","quote","fullUrl","fontFilename","fontFile","fontUrl","fontKey","fontResp","fontBlob","fontExt","fontContentType","fontErr","error","key","lid","orphaned","layouts","downloadedBytes","chunkRanges","start","chunkMap","nextChunkIndex","downloadChunk","range","rangeHeader","chunkResponse","chunkBlob","progress","downloadNext","downloaders","orderedChunks","loaded","total","complete","cacheManager","log","createLogger","ServiceWorkerBackend","registration","payload","data","messageChannel","success","enqueuedCount","activeCount","queuedCount","fileType","fileId","mediaIds","CacheProxy","_b","swReady","timeout","_","files","channel","deleted","controller","warmed"],"mappings":"iDAKO,MAAMA,CAAa,CACxB,aAAc,CACZ,KAAK,OAAS,IAAI,GACpB,CAOA,GAAGC,EAAOC,EAAU,CACb,KAAK,OAAO,IAAID,CAAK,GACxB,KAAK,OAAO,IAAIA,EAAO,CAAA,CAAE,EAE3B,KAAK,OAAO,IAAIA,CAAK,EAAE,KAAKC,CAAQ,CACtC,CAOA,KAAKD,EAAOC,EAAU,CACpB,MAAMC,EAAU,IAAIC,IAAS,CAC3BF,EAAS,GAAGE,CAAI,EAChB,KAAK,IAAIH,EAAOE,CAAO,CACzB,EACA,KAAK,GAAGF,EAAOE,CAAO,CACxB,CAOA,IAAIF,EAAOC,EAAU,CACnB,GAAI,CAAC,KAAK,OAAO,IAAID,CAAK,EAAG,OAE7B,MAAMI,EAAY,KAAK,OAAO,IAAIJ,CAAK,EACjCK,EAAQD,EAAU,QAAQH,CAAQ,EACpCI,IAAU,IACZD,EAAU,OAAOC,EAAO,CAAC,CAE7B,CAOA,KAAKL,KAAUG,EAAM,CACnB,GAAI,CAAC,KAAK,OAAO,IAAIH,CAAK,EAAG,OAG7B,MAAMI,EAAY,KAAK,OAAO,IAAIJ,CAAK,EAAE,MAAK,EAC9C,UAAWM,KAAYF,EACrBE,EAAS,GAAGH,CAAI,CAEpB,CAMA,mBAAmBH,EAAO,CACpBA,EACF,KAAK,OAAO,OAAOA,CAAK,EAExB,KAAK,OAAO,MAAK,CAErB,CACF,wIC5EC,SAAUO,EAAS,CAGZC,EAAA,QAAiBD,EAAO,CAgBhC,GAAE,SAAUE,EAAW,KAoBfC,EAAU,CAAC,IAAK,IAAK,IAAK,IAAK,IAAK,IAAK,IAAK,IAAK,IAAK,IAAK,IAAK,IAAK,IAAK,IAAK,IAAK,GAAG,EAQ7F,SAASC,EAASC,EAAGC,EAAG,CACpB,IAAIC,EAAIF,EAAE,CAAC,EACPG,EAAIH,EAAE,CAAC,EACPI,EAAIJ,EAAE,CAAC,EACPK,EAAIL,EAAE,CAAC,EAEXE,IAAMC,EAAIC,EAAI,CAACD,EAAIE,GAAKJ,EAAE,CAAC,EAAI,UAAY,EAC3CC,GAAMA,GAAK,EAAIA,IAAM,IAAMC,EAAI,EAC/BE,IAAMH,EAAIC,EAAI,CAACD,EAAIE,GAAKH,EAAE,CAAC,EAAI,UAAY,EAC3CI,GAAMA,GAAK,GAAKA,IAAM,IAAMH,EAAI,EAChCE,IAAMC,EAAIH,EAAI,CAACG,EAAIF,GAAKF,EAAE,CAAC,EAAI,UAAY,EAC3CG,GAAMA,GAAK,GAAKA,IAAM,IAAMC,EAAI,EAChCF,IAAMC,EAAIC,EAAI,CAACD,EAAIF,GAAKD,EAAE,CAAC,EAAI,WAAa,EAC5CE,GAAMA,GAAK,GAAKA,IAAM,IAAMC,EAAI,EAChCF,IAAMC,EAAIC,EAAI,CAACD,EAAIE,GAAKJ,EAAE,CAAC,EAAI,UAAY,EAC3CC,GAAMA,GAAK,EAAIA,IAAM,IAAMC,EAAI,EAC/BE,IAAMH,EAAIC,EAAI,CAACD,EAAIE,GAAKH,EAAE,CAAC,EAAI,WAAa,EAC5CI,GAAMA,GAAK,GAAKA,IAAM,IAAMH,EAAI,EAChCE,IAAMC,EAAIH,EAAI,CAACG,EAAIF,GAAKF,EAAE,CAAC,EAAI,WAAa,EAC5CG,GAAMA,GAAK,GAAKA,IAAM,IAAMC,EAAI,EAChCF,IAAMC,EAAIC,EAAI,CAACD,EAAIF,GAAKD,EAAE,CAAC,EAAI,SAAW,EAC1CE,GAAMA,GAAK,GAAKA,IAAM,IAAMC,EAAI,EAChCF,IAAMC,EAAIC,EAAI,CAACD,EAAIE,GAAKJ,EAAE,CAAC,EAAI,WAAa,EAC5CC,GAAMA,GAAK,EAAIA,IAAM,IAAMC,EAAI,EAC/BE,IAAMH,EAAIC,EAAI,CAACD,EAAIE,GAAKH,EAAE,CAAC,EAAI,WAAa,EAC5CI,GAAMA,GAAK,GAAKA,IAAM,IAAMH,EAAI,EAChCE,IAAMC,EAAIH,EAAI,CAACG,EAAIF,GAAKF,EAAE,EAAE,EAAI,MAAQ,EACxCG,GAAMA,GAAK,GAAKA,IAAM,IAAMC,EAAI,EAChCF,IAAMC,EAAIC,EAAI,CAACD,EAAIF,GAAKD,EAAE,EAAE,EAAI,WAAa,EAC7CE,GAAMA,GAAK,GAAKA,IAAM,IAAMC,EAAI,EAChCF,IAAMC,EAAIC,EAAI,CAACD,EAAIE,GAAKJ,EAAE,EAAE,EAAI,WAAa,EAC7CC,GAAMA,GAAK,EAAIA,IAAM,IAAMC,EAAI,EAC/BE,IAAMH,EAAIC,EAAI,CAACD,EAAIE,GAAKH,EAAE,EAAE,EAAI,SAAW,EAC3CI,GAAMA,GAAK,GAAKA,IAAM,IAAMH,EAAI,EAChCE,IAAMC,EAAIH,EAAI,CAACG,EAAIF,GAAKF,EAAE,EAAE,EAAI,WAAa,EAC7CG,GAAMA,GAAK,GAAKA,IAAM,IAAMC,EAAI,EAChCF,IAAMC,EAAIC,EAAI,CAACD,EAAIF,GAAKD,EAAE,EAAE,EAAI,WAAa,EAC7CE,GAAMA,GAAK,GAAKA,IAAM,IAAMC,EAAI,EAEhCF,IAAMC,EAAIE,EAAID,EAAI,CAACC,GAAKJ,EAAE,CAAC,EAAI,UAAY,EAC3CC,GAAMA,GAAK,EAAIA,IAAM,IAAMC,EAAI,EAC/BE,IAAMH,EAAIE,EAAID,EAAI,CAACC,GAAKH,EAAE,CAAC,EAAI,WAAa,EAC5CI,GAAMA,GAAK,EAAIA,IAAM,IAAMH,EAAI,EAC/BE,IAAMC,EAAIF,EAAID,EAAI,CAACC,GAAKF,EAAE,EAAE,EAAI,UAAY,EAC5CG,GAAMA,GAAK,GAAKA,IAAM,IAAMC,EAAI,EAChCF,IAAMC,EAAIF,EAAIG,EAAI,CAACH,GAAKD,EAAE,CAAC,EAAI,UAAY,EAC3CE,GAAMA,GAAK,GAAKA,IAAM,IAAMC,EAAI,EAChCF,IAAMC,EAAIE,EAAID,EAAI,CAACC,GAAKJ,EAAE,CAAC,EAAI,UAAY,EAC3CC,GAAMA,GAAK,EAAIA,IAAM,IAAMC,EAAI,EAC/BE,IAAMH,EAAIE,EAAID,EAAI,CAACC,GAAKH,EAAE,EAAE,EAAI,SAAW,EAC3CI,GAAMA,GAAK,EAAIA,IAAM,IAAMH,EAAI,EAC/BE,IAAMC,EAAIF,EAAID,EAAI,CAACC,GAAKF,EAAE,EAAE,EAAI,UAAY,EAC5CG,GAAMA,GAAK,GAAKA,IAAM,IAAMC,EAAI,EAChCF,IAAMC,EAAIF,EAAIG,EAAI,CAACH,GAAKD,EAAE,CAAC,EAAI,UAAY,EAC3CE,GAAMA,GAAK,GAAKA,IAAM,IAAMC,EAAI,EAChCF,IAAMC,EAAIE,EAAID,EAAI,CAACC,GAAKJ,EAAE,CAAC,EAAI,UAAY,EAC3CC,GAAMA,GAAK,EAAIA,IAAM,IAAMC,EAAI,EAC/BE,IAAMH,EAAIE,EAAID,EAAI,CAACC,GAAKH,EAAE,EAAE,EAAI,WAAa,EAC7CI,GAAMA,GAAK,EAAIA,IAAM,IAAMH,EAAI,EAC/BE,IAAMC,EAAIF,EAAID,EAAI,CAACC,GAAKF,EAAE,CAAC,EAAI,UAAY,EAC3CG,GAAMA,GAAK,GAAKA,IAAM,IAAMC,EAAI,EAChCF,IAAMC,EAAIF,EAAIG,EAAI,CAACH,GAAKD,EAAE,CAAC,EAAI,WAAa,EAC5CE,GAAMA,GAAK,GAAKA,IAAM,IAAMC,EAAI,EAChCF,IAAMC,EAAIE,EAAID,EAAI,CAACC,GAAKJ,EAAE,EAAE,EAAI,WAAa,EAC7CC,GAAMA,GAAK,EAAIA,IAAM,IAAMC,EAAI,EAC/BE,IAAMH,EAAIE,EAAID,EAAI,CAACC,GAAKH,EAAE,CAAC,EAAI,SAAW,EAC1CI,GAAMA,GAAK,EAAIA,IAAM,IAAMH,EAAI,EAC/BE,IAAMC,EAAIF,EAAID,EAAI,CAACC,GAAKF,EAAE,CAAC,EAAI,WAAa,EAC5CG,GAAMA,GAAK,GAAKA,IAAM,IAAMC,EAAI,EAChCF,IAAMC,EAAIF,EAAIG,EAAI,CAACH,GAAKD,EAAE,EAAE,EAAI,WAAa,EAC7CE,GAAMA,GAAK,GAAKA,IAAM,IAAMC,EAAI,EAEhCF,IAAMC,EAAIC,EAAIC,GAAKJ,EAAE,CAAC,EAAI,OAAS,EACnCC,GAAMA,GAAK,EAAIA,IAAM,IAAMC,EAAI,EAC/BE,IAAMH,EAAIC,EAAIC,GAAKH,EAAE,CAAC,EAAI,WAAa,EACvCI,GAAMA,GAAK,GAAKA,IAAM,IAAMH,EAAI,EAChCE,IAAMC,EAAIH,EAAIC,GAAKF,EAAE,EAAE,EAAI,WAAa,EACxCG,GAAMA,GAAK,GAAKA,IAAM,IAAMC,EAAI,EAChCF,IAAMC,EAAIC,EAAIH,GAAKD,EAAE,EAAE,EAAI,SAAW,EACtCE,GAAMA,GAAK,GAAKA,IAAM,GAAKC,EAAI,EAC/BF,IAAMC,EAAIC,EAAIC,GAAKJ,EAAE,CAAC,EAAI,WAAa,EACvCC,GAAMA,GAAK,EAAIA,IAAM,IAAMC,EAAI,EAC/BE,IAAMH,EAAIC,EAAIC,GAAKH,EAAE,CAAC,EAAI,WAAa,EACvCI,GAAMA,GAAK,GAAKA,IAAM,IAAMH,EAAI,EAChCE,IAAMC,EAAIH,EAAIC,GAAKF,EAAE,CAAC,EAAI,UAAY,EACtCG,GAAMA,GAAK,GAAKA,IAAM,IAAMC,EAAI,EAChCF,IAAMC,EAAIC,EAAIH,GAAKD,EAAE,EAAE,EAAI,WAAa,EACxCE,GAAMA,GAAK,GAAKA,IAAM,GAAKC,EAAI,EAC/BF,IAAMC,EAAIC,EAAIC,GAAKJ,EAAE,EAAE,EAAI,UAAY,EACvCC,GAAMA,GAAK,EAAIA,IAAM,IAAMC,EAAI,EAC/BE,IAAMH,EAAIC,EAAIC,GAAKH,EAAE,CAAC,EAAI,UAAY,EACtCI,GAAMA,GAAK,GAAKA,IAAM,IAAMH,EAAI,EAChCE,IAAMC,EAAIH,EAAIC,GAAKF,EAAE,CAAC,EAAI,UAAY,EACtCG,GAAMA,GAAK,GAAKA,IAAM,IAAMC,EAAI,EAChCF,IAAMC,EAAIC,EAAIH,GAAKD,EAAE,CAAC,EAAI,SAAW,EACrCE,GAAMA,GAAK,GAAKA,IAAM,GAAKC,EAAI,EAC/BF,IAAMC,EAAIC,EAAIC,GAAKJ,EAAE,CAAC,EAAI,UAAY,EACtCC,GAAMA,GAAK,EAAIA,IAAM,IAAMC,EAAI,EAC/BE,IAAMH,EAAIC,EAAIC,GAAKH,EAAE,EAAE,EAAI,UAAY,EACvCI,GAAMA,GAAK,GAAKA,IAAM,IAAMH,EAAI,EAChCE,IAAMC,EAAIH,EAAIC,GAAKF,EAAE,EAAE,EAAI,UAAY,EACvCG,GAAMA,GAAK,GAAKA,IAAM,IAAMC,EAAI,EAChCF,IAAMC,EAAIC,EAAIH,GAAKD,EAAE,CAAC,EAAI,UAAY,EACtCE,GAAMA,GAAK,GAAKA,IAAM,GAAKC,EAAI,EAE/BF,IAAME,GAAKD,EAAI,CAACE,IAAMJ,EAAE,CAAC,EAAI,UAAY,EACzCC,GAAMA,GAAK,EAAIA,IAAM,IAAMC,EAAI,EAC/BE,IAAMF,GAAKD,EAAI,CAACE,IAAMH,EAAE,CAAC,EAAI,WAAa,EAC1CI,GAAMA,GAAK,GAAKA,IAAM,IAAMH,EAAI,EAChCE,IAAMF,GAAKG,EAAI,CAACF,IAAMF,EAAE,EAAE,EAAI,WAAa,EAC3CG,GAAMA,GAAK,GAAKA,IAAM,IAAMC,EAAI,EAChCF,IAAME,GAAKD,EAAI,CAACF,IAAMD,EAAE,CAAC,EAAI,SAAW,EACxCE,GAAMA,GAAK,GAAIA,IAAM,IAAMC,EAAI,EAC/BF,IAAME,GAAKD,EAAI,CAACE,IAAMJ,EAAE,EAAE,EAAI,WAAa,EAC3CC,GAAMA,GAAK,EAAIA,IAAM,IAAMC,EAAI,EAC/BE,IAAMF,GAAKD,EAAI,CAACE,IAAMH,EAAE,CAAC,EAAI,WAAa,EAC1CI,GAAMA,GAAK,GAAKA,IAAM,IAAMH,EAAI,EAChCE,IAAMF,GAAKG,EAAI,CAACF,IAAMF,EAAE,EAAE,EAAI,QAAU,EACxCG,GAAMA,GAAK,GAAKA,IAAM,IAAMC,EAAI,EAChCF,IAAME,GAAKD,EAAI,CAACF,IAAMD,EAAE,CAAC,EAAI,WAAa,EAC1CE,GAAMA,GAAK,GAAIA,IAAM,IAAMC,EAAI,EAC/BF,IAAME,GAAKD,EAAI,CAACE,IAAMJ,EAAE,CAAC,EAAI,WAAa,EAC1CC,GAAMA,GAAK,EAAIA,IAAM,IAAMC,EAAI,EAC/BE,IAAMF,GAAKD,EAAI,CAACE,IAAMH,EAAE,EAAE,EAAI,SAAW,EACzCI,GAAMA,GAAK,GAAKA,IAAM,IAAMH,EAAI,EAChCE,IAAMF,GAAKG,EAAI,CAACF,IAAMF,EAAE,CAAC,EAAI,WAAa,EAC1CG,GAAMA,GAAK,GAAKA,IAAM,IAAMC,EAAI,EAChCF,IAAME,GAAKD,EAAI,CAACF,IAAMD,EAAE,EAAE,EAAI,WAAa,EAC3CE,GAAMA,GAAK,GAAIA,IAAM,IAAMC,EAAI,EAC/BF,IAAME,GAAKD,EAAI,CAACE,IAAMJ,EAAE,CAAC,EAAI,UAAY,EACzCC,GAAMA,GAAK,EAAIA,IAAM,IAAMC,EAAI,EAC/BE,IAAMF,GAAKD,EAAI,CAACE,IAAMH,EAAE,EAAE,EAAI,WAAa,EAC3CI,GAAMA,GAAK,GAAKA,IAAM,IAAMH,EAAI,EAChCE,IAAMF,GAAKG,EAAI,CAACF,IAAMF,EAAE,CAAC,EAAI,UAAY,EACzCG,GAAMA,GAAK,GAAKA,IAAM,IAAMC,EAAI,EAChCF,IAAME,GAAKD,EAAI,CAACF,IAAMD,EAAE,CAAC,EAAI,UAAY,EACzCE,GAAMA,GAAK,GAAKA,IAAM,IAAMC,EAAI,EAEhCJ,EAAE,CAAC,EAAIE,EAAIF,EAAE,CAAC,EAAI,EAClBA,EAAE,CAAC,EAAIG,EAAIH,EAAE,CAAC,EAAI,EAClBA,EAAE,CAAC,EAAII,EAAIJ,EAAE,CAAC,EAAI,EAClBA,EAAE,CAAC,EAAIK,EAAIL,EAAE,CAAC,EAAI,CAC1B,CAEI,SAASM,EAAOC,EAAG,CACf,IAAIC,EAAU,CAAA,EACVC,EAEJ,IAAKA,EAAI,EAAGA,EAAI,GAAIA,GAAK,EACrBD,EAAQC,GAAK,CAAC,EAAIF,EAAE,WAAWE,CAAC,GAAKF,EAAE,WAAWE,EAAI,CAAC,GAAK,IAAMF,EAAE,WAAWE,EAAI,CAAC,GAAK,KAAOF,EAAE,WAAWE,EAAI,CAAC,GAAK,IAE3H,OAAOD,CACf,CAEI,SAASE,EAAaR,EAAG,CACrB,IAAIM,EAAU,CAAA,EACVC,EAEJ,IAAKA,EAAI,EAAGA,EAAI,GAAIA,GAAK,EACrBD,EAAQC,GAAK,CAAC,EAAIP,EAAEO,CAAC,GAAKP,EAAEO,EAAI,CAAC,GAAK,IAAMP,EAAEO,EAAI,CAAC,GAAK,KAAOP,EAAEO,EAAI,CAAC,GAAK,IAE/E,OAAOD,CACf,CAEI,SAASG,EAAKJ,EAAG,CACb,IAAIK,EAAIL,EAAE,OACNM,EAAQ,CAAC,WAAY,WAAY,YAAa,SAAS,EACvDJ,EACAK,EACAC,EACAC,EACAC,EACAC,EAEJ,IAAKT,EAAI,GAAIA,GAAKG,EAAGH,GAAK,GACtBV,EAASc,EAAOP,EAAOC,EAAE,UAAUE,EAAI,GAAIA,CAAC,CAAC,CAAC,EAKlD,IAHAF,EAAIA,EAAE,UAAUE,EAAI,EAAE,EACtBK,EAASP,EAAE,OACXQ,EAAO,CAAC,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,CAAC,EACjDN,EAAI,EAAGA,EAAIK,EAAQL,GAAK,EACzBM,EAAKN,GAAK,CAAC,GAAKF,EAAE,WAAWE,CAAC,IAAOA,EAAI,GAAM,GAGnD,GADAM,EAAKN,GAAK,CAAC,GAAK,MAAUA,EAAI,GAAM,GAChCA,EAAI,GAEJ,IADAV,EAASc,EAAOE,CAAI,EACfN,EAAI,EAAGA,EAAI,GAAIA,GAAK,EACrBM,EAAKN,CAAC,EAAI,EAKlB,OAAAO,EAAMJ,EAAI,EACVI,EAAMA,EAAI,SAAS,EAAE,EAAE,MAAM,gBAAgB,EAC7CC,EAAK,SAASD,EAAI,CAAC,EAAG,EAAE,EACxBE,EAAK,SAASF,EAAI,CAAC,EAAG,EAAE,GAAK,EAE7BD,EAAK,EAAE,EAAIE,EACXF,EAAK,EAAE,EAAIG,EAEXnB,EAASc,EAAOE,CAAI,EACbF,CACf,CAEI,SAASM,EAAWjB,EAAG,CACnB,IAAIU,EAAIV,EAAE,OACNW,EAAQ,CAAC,WAAY,WAAY,YAAa,SAAS,EACvDJ,EACAK,EACAC,EACAC,EACAC,EACAC,EAEJ,IAAKT,EAAI,GAAIA,GAAKG,EAAGH,GAAK,GACtBV,EAASc,EAAOH,EAAaR,EAAE,SAASO,EAAI,GAAIA,CAAC,CAAC,CAAC,EAWvD,IAJAP,EAAKO,EAAI,GAAMG,EAAIV,EAAE,SAASO,EAAI,EAAE,EAAI,IAAI,WAAW,CAAC,EAExDK,EAASZ,EAAE,OACXa,EAAO,CAAC,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,CAAC,EACjDN,EAAI,EAAGA,EAAIK,EAAQL,GAAK,EACzBM,EAAKN,GAAK,CAAC,GAAKP,EAAEO,CAAC,IAAOA,EAAI,GAAM,GAIxC,GADAM,EAAKN,GAAK,CAAC,GAAK,MAAUA,EAAI,GAAM,GAChCA,EAAI,GAEJ,IADAV,EAASc,EAAOE,CAAI,EACfN,EAAI,EAAGA,EAAI,GAAIA,GAAK,EACrBM,EAAKN,CAAC,EAAI,EAKlB,OAAAO,EAAMJ,EAAI,EACVI,EAAMA,EAAI,SAAS,EAAE,EAAE,MAAM,gBAAgB,EAC7CC,EAAK,SAASD,EAAI,CAAC,EAAG,EAAE,EACxBE,EAAK,SAASF,EAAI,CAAC,EAAG,EAAE,GAAK,EAE7BD,EAAK,EAAE,EAAIE,EACXF,EAAK,EAAE,EAAIG,EAEXnB,EAASc,EAAOE,CAAI,EAEbF,CACf,CAEI,SAASO,EAAKR,EAAG,CACb,IAAIL,EAAI,GACJc,EACJ,IAAKA,EAAI,EAAGA,EAAI,EAAGA,GAAK,EACpBd,GAAKT,EAASc,GAAMS,EAAI,EAAI,EAAM,EAAI,EAAIvB,EAASc,GAAMS,EAAI,EAAM,EAAI,EAE3E,OAAOd,CACf,CAEI,SAASe,EAAItB,EAAG,CACZ,IAAIS,EACJ,IAAKA,EAAI,EAAGA,EAAIT,EAAE,OAAQS,GAAK,EAC3BT,EAAES,CAAC,EAAIW,EAAKpB,EAAES,CAAC,CAAC,EAEpB,OAAOT,EAAE,KAAK,EAAE,CACxB,CAGQsB,EAAIX,EAAK,OAAO,CAAC,EAgBjB,OAAO,YAAgB,KAAe,CAAC,YAAY,UAAU,OAC5D,UAAY,CACT,SAASY,EAAMC,EAAKV,EAAQ,CAGxB,OAFAU,EAAOA,EAAM,GAAM,EAEfA,EAAM,EACC,KAAK,IAAIA,EAAMV,EAAQ,CAAC,EAG5B,KAAK,IAAIU,EAAKV,CAAM,CAC3C,CAEY,YAAY,UAAU,MAAQ,SAAUW,EAAMC,EAAI,CAC9C,IAAIZ,EAAS,KAAK,WACda,EAAQJ,EAAME,EAAMX,CAAM,EAC1Bc,EAAMd,EACNe,EACAC,EACAC,EACAC,EAMJ,OAJIN,IAAO7B,IACP+B,EAAML,EAAMG,EAAIZ,CAAM,GAGtBa,EAAQC,EACD,IAAI,YAAY,CAAC,GAG5BC,EAAMD,EAAMD,EACZG,EAAS,IAAI,YAAYD,CAAG,EAC5BE,EAAc,IAAI,WAAWD,CAAM,EAEnCE,EAAc,IAAI,WAAW,KAAML,EAAOE,CAAG,EAC7CE,EAAY,IAAIC,CAAW,EAEpBF,EACvB,CACA,EAAS,EASL,SAASG,EAAOC,EAAK,CACjB,MAAI,kBAAkB,KAAKA,CAAG,IAC1BA,EAAM,SAAS,mBAAmBA,CAAG,CAAC,GAGnCA,CACf,CAEI,SAASC,EAAoBD,EAAKE,EAAkB,CAChD,IAAItB,EAASoB,EAAI,OACdG,EAAO,IAAI,YAAYvB,CAAM,EAC7BwB,EAAM,IAAI,WAAWD,CAAI,EACzB5B,EAEH,IAAKA,EAAI,EAAGA,EAAIK,EAAQL,GAAK,EACzB6B,EAAI7B,CAAC,EAAIyB,EAAI,WAAWzB,CAAC,EAG7B,OAAO2B,EAAmBE,EAAMD,CACxC,CAEI,SAASE,EAAoBF,EAAM,CAC/B,OAAO,OAAO,aAAa,MAAM,KAAM,IAAI,WAAWA,CAAI,CAAC,CACnE,CAEI,SAASG,EAAwBC,EAAOC,EAAQN,EAAkB,CAC9D,IAAIO,EAAS,IAAI,WAAWF,EAAM,WAAaC,EAAO,UAAU,EAEhE,OAAAC,EAAO,IAAI,IAAI,WAAWF,CAAK,CAAC,EAChCE,EAAO,IAAI,IAAI,WAAWD,CAAM,EAAGD,EAAM,UAAU,EAEzBE,CAClC,CAEI,SAASC,EAAkBtB,EAAK,CAC5B,IAAIuB,EAAQ,CAAA,EACR/B,EAASQ,EAAI,OACbtB,EAEJ,IAAKA,EAAI,EAAGA,EAAIc,EAAS,EAAGd,GAAK,EAC7B6C,EAAM,KAAK,SAASvB,EAAI,OAAOtB,EAAG,CAAC,EAAG,EAAE,CAAC,EAG7C,OAAO,OAAO,aAAa,MAAM,OAAQ6C,CAAK,CACtD,CAWI,SAASC,GAAW,CAEhB,KAAK,MAAK,CAClB,CAUI,OAAAA,EAAS,UAAU,OAAS,SAAUZ,EAAK,CAGvC,YAAK,aAAaD,EAAOC,CAAG,CAAC,EAEtB,IACf,EASIY,EAAS,UAAU,aAAe,SAAUC,EAAU,CAClD,KAAK,OAASA,EACd,KAAK,SAAWA,EAAS,OAEzB,IAAIjC,EAAS,KAAK,MAAM,OACpBL,EAEJ,IAAKA,EAAI,GAAIA,GAAKK,EAAQL,GAAK,GAC3BV,EAAS,KAAK,MAAOO,EAAO,KAAK,MAAM,UAAUG,EAAI,GAAIA,CAAC,CAAC,CAAC,EAGhE,YAAK,MAAQ,KAAK,MAAM,UAAUA,EAAI,EAAE,EAEjC,IACf,EAUIqC,EAAS,UAAU,IAAM,SAAUE,EAAK,CACpC,IAAIX,EAAO,KAAK,MACZvB,EAASuB,EAAK,OACd5B,EACAM,EAAO,CAAC,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,CAAC,EACtDkC,EAEJ,IAAKxC,EAAI,EAAGA,EAAIK,EAAQL,GAAK,EACzBM,EAAKN,GAAK,CAAC,GAAK4B,EAAK,WAAW5B,CAAC,IAAOA,EAAI,GAAM,GAGtD,YAAK,QAAQM,EAAMD,CAAM,EACzBmC,EAAM3B,EAAI,KAAK,KAAK,EAEhB0B,IACAC,EAAML,EAAkBK,CAAG,GAG/B,KAAK,MAAK,EAEHA,CACf,EAOIH,EAAS,UAAU,MAAQ,UAAY,CACnC,YAAK,MAAQ,GACb,KAAK,QAAU,EACf,KAAK,MAAQ,CAAC,WAAY,WAAY,YAAa,SAAS,EAErD,IACf,EAOIA,EAAS,UAAU,SAAW,UAAY,CACtC,MAAO,CACH,KAAM,KAAK,MACX,OAAQ,KAAK,QACb,KAAM,KAAK,MAAM,MAAK,CAClC,CACA,EASIA,EAAS,UAAU,SAAW,SAAUjC,EAAO,CAC3C,YAAK,MAAQA,EAAM,KACnB,KAAK,QAAUA,EAAM,OACrB,KAAK,MAAQA,EAAM,KAEZ,IACf,EAMIiC,EAAS,UAAU,QAAU,UAAY,CACrC,OAAO,KAAK,MACZ,OAAO,KAAK,MACZ,OAAO,KAAK,OACpB,EAQIA,EAAS,UAAU,QAAU,SAAU/B,EAAMD,EAAQ,CACjD,IAAIL,EAAIK,EACJE,EACAC,EACAC,EAGJ,GADAH,EAAKN,GAAK,CAAC,GAAK,MAAUA,EAAI,GAAM,GAChCA,EAAI,GAEJ,IADAV,EAAS,KAAK,MAAOgB,CAAI,EACpBN,EAAI,EAAGA,EAAI,GAAIA,GAAK,EACrBM,EAAKN,CAAC,EAAI,EAMlBO,EAAM,KAAK,QAAU,EACrBA,EAAMA,EAAI,SAAS,EAAE,EAAE,MAAM,gBAAgB,EAC7CC,EAAK,SAASD,EAAI,CAAC,EAAG,EAAE,EACxBE,EAAK,SAASF,EAAI,CAAC,EAAG,EAAE,GAAK,EAE7BD,EAAK,EAAE,EAAIE,EACXF,EAAK,EAAE,EAAIG,EACXnB,EAAS,KAAK,MAAOgB,CAAI,CACjC,EAWI+B,EAAS,KAAO,SAAUZ,EAAKc,EAAK,CAGhC,OAAOF,EAAS,WAAWb,EAAOC,CAAG,EAAGc,CAAG,CACnD,EAUIF,EAAS,WAAa,SAAUI,EAASF,EAAK,CAC1C,IAAIG,EAAOxC,EAAKuC,CAAO,EACnBD,EAAM3B,EAAI6B,CAAI,EAElB,OAAOH,EAAMJ,EAAkBK,CAAG,EAAIA,CAC9C,EASIH,EAAS,YAAc,UAAY,CAE/B,KAAK,MAAK,CAClB,EASIA,EAAS,YAAY,UAAU,OAAS,SAAUR,EAAK,CACnD,IAAID,EAAOG,EAAwB,KAAK,MAAM,OAAQF,CAAS,EAC3DxB,EAASuB,EAAK,OACd5B,EAIJ,IAFA,KAAK,SAAW6B,EAAI,WAEf7B,EAAI,GAAIA,GAAKK,EAAQL,GAAK,GAC3BV,EAAS,KAAK,MAAOW,EAAa2B,EAAK,SAAS5B,EAAI,GAAIA,CAAC,CAAC,CAAC,EAG/D,YAAK,MAASA,EAAI,GAAMK,EAAS,IAAI,WAAWuB,EAAK,OAAO,MAAM5B,EAAI,EAAE,CAAC,EAAI,IAAI,WAAW,CAAC,EAEtF,IACf,EAUIqC,EAAS,YAAY,UAAU,IAAM,SAAUE,EAAK,CAChD,IAAIX,EAAO,KAAK,MACZvB,EAASuB,EAAK,OACdtB,EAAO,CAAC,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,CAAC,EACtDN,EACAwC,EAEJ,IAAKxC,EAAI,EAAGA,EAAIK,EAAQL,GAAK,EACzBM,EAAKN,GAAK,CAAC,GAAK4B,EAAK5B,CAAC,IAAOA,EAAI,GAAM,GAG3C,YAAK,QAAQM,EAAMD,CAAM,EACzBmC,EAAM3B,EAAI,KAAK,KAAK,EAEhB0B,IACAC,EAAML,EAAkBK,CAAG,GAG/B,KAAK,MAAK,EAEHA,CACf,EAOIH,EAAS,YAAY,UAAU,MAAQ,UAAY,CAC/C,YAAK,MAAQ,IAAI,WAAW,CAAC,EAC7B,KAAK,QAAU,EACf,KAAK,MAAQ,CAAC,WAAY,WAAY,YAAa,SAAS,EAErD,IACf,EAOIA,EAAS,YAAY,UAAU,SAAW,UAAY,CAClD,IAAIjC,EAAQiC,EAAS,UAAU,SAAS,KAAK,IAAI,EAGjD,OAAAjC,EAAM,KAAO0B,EAAoB1B,EAAM,IAAI,EAEpCA,CACf,EASIiC,EAAS,YAAY,UAAU,SAAW,SAAUjC,EAAO,CAEvD,OAAAA,EAAM,KAAOsB,EAAoBtB,EAAM,KAAM,EAAI,EAE1CiC,EAAS,UAAU,SAAS,KAAK,KAAMjC,CAAK,CAC3D,EAEIiC,EAAS,YAAY,UAAU,QAAUA,EAAS,UAAU,QAE5DA,EAAS,YAAY,UAAU,QAAUA,EAAS,UAAU,QAU5DA,EAAS,YAAY,KAAO,SAAUR,EAAKU,EAAK,CAC5C,IAAIG,EAAOhC,EAAW,IAAI,WAAWmB,CAAG,CAAC,EACrCW,EAAM3B,EAAI6B,CAAI,EAElB,OAAOH,EAAMJ,EAAkBK,CAAG,EAAIA,CAC9C,EAEWH,CACX,CAAC,oCCvuBKM,EAAa,gBACbC,EAAU,cACVC,EAAa,EACbC,EAAc,QACdC,EAAoB,EAGpBC,EAAQ,OAAO,OAAW,KAC5B,OAAO,SAAS,SAAS,QAAQ,WAAY,EAAE,EAAE,QAAQ,MAAO,EAAE,GAAK,cAGpE,MAAMC,CAAa,CACxB,aAAc,CACZ,KAAK,MAAQ,KACb,KAAK,GAAK,KAEV,KAAK,WAAa,IAAI,GACxB,CAMA,gBAAgBC,EAAK,CACnB,GAAI,CAGF,OAFe,IAAI,IAAIA,CAAG,EACD,aAAa,IAAI,MAAM,GAC5B,SACtB,MAAY,CACV,MAAO,SACT,CACF,CAMA,WAAWA,EAAK,CACd,GAAI,CAACA,EAAK,OAAOA,EAGjB,GAAI,CACF,MAAMC,EAAS,IAAI,IAAID,CAAG,EACpBE,EAAY,IAAI,IAAIC,EAAO,UAAU,EAG3C,GAAIF,EAAO,SAAWC,EAAU,OAC9B,eAAQ,IAAI,0BAA0BD,EAAO,MAAM,MAAMC,EAAU,MAAM,EAAE,EAC3ED,EAAO,SAAWC,EAAU,SAC5BD,EAAO,SAAWC,EAAU,SAC5BD,EAAO,KAAOC,EAAU,KACjBD,EAAO,SAAQ,CAE1B,MAAY,CAEZ,CAEA,OAAOD,CACT,CAKA,MAAM,MAAO,CACX,KAAK,MAAQ,MAAM,OAAO,KAAKP,CAAU,EACzC,KAAK,GAAK,MAAM,KAAK,OAAM,CAC7B,CAKA,QAAS,CACP,OAAO,IAAI,QAAQ,CAACW,EAASC,IAAW,CACtC,MAAMC,EAAU,UAAU,KAAKZ,EAASC,CAAU,EAElDW,EAAQ,QAAU,IAAMD,EAAOC,EAAQ,KAAK,EAC5CA,EAAQ,UAAY,IAAMF,EAAQE,EAAQ,MAAM,EAEhDA,EAAQ,gBAAmB7E,GAAU,CACnC,MAAM8E,EAAK9E,EAAM,OAAO,OACnB8E,EAAG,iBAAiB,SAASX,CAAW,GAC7BW,EAAG,kBAAkBX,EAAa,CAAE,QAAS,KAAM,EAC3D,YAAY,OAAQ,OAAQ,CAAE,OAAQ,GAAO,CAEvD,CACF,CAAC,CACH,CAKA,MAAM,QAAQY,EAAI,CAChB,OAAO,IAAI,QAAQ,CAACJ,EAASC,IAAW,CAGtC,MAAMC,EAFK,KAAK,GAAG,YAAYV,EAAa,UAAU,EACrC,YAAYA,CAAW,EAClB,IAAIY,CAAE,EAC5BF,EAAQ,UAAY,IAAMF,EAAQE,EAAQ,MAAM,EAChDA,EAAQ,QAAU,IAAMD,EAAOC,EAAQ,KAAK,CAC9C,CAAC,CACH,CAKA,MAAM,SAASG,EAAY,CACzB,OAAO,IAAI,QAAQ,CAACL,EAASC,IAAW,CAGtC,MAAMC,EAFK,KAAK,GAAG,YAAYV,EAAa,WAAW,EACtC,YAAYA,CAAW,EAClB,IAAIa,CAAU,EACpCH,EAAQ,UAAY,IAAMF,EAAO,EACjCE,EAAQ,QAAU,IAAMD,EAAOC,EAAQ,KAAK,CAC9C,CAAC,CACH,CAKA,MAAM,aAAc,CAClB,OAAO,IAAI,QAAQ,CAACF,EAASC,IAAW,CAGtC,MAAMC,EAFK,KAAK,GAAG,YAAYV,EAAa,UAAU,EACrC,YAAYA,CAAW,EAClB,OAAM,EAC5BU,EAAQ,UAAY,IAAMF,EAAQE,EAAQ,MAAM,EAChDA,EAAQ,QAAU,IAAMD,EAAOC,EAAQ,KAAK,CAC9C,CAAC,CACH,CASA,MAAM,aAAaI,EAAU,OAC3B,KAAM,CAAE,GAAAF,EAAI,KAAAG,EAAM,KAAAC,EAAM,IAAAC,EAAK,SAAAC,CAAQ,EAAKJ,EAG1C,GAAI,OAAO,UAAc,OAAeK,EAAA,UAAU,gBAAV,MAAAA,EAAyB,YAC/D,eAAQ,IAAI,gEAAgEJ,CAAI,IAAIH,CAAE,EAAE,EACxF,QAAQ,IAAI,iEAAiE,EACtE,CACL,GAAAA,EACA,KAAAG,EACA,KAAAC,EACA,IAAKC,GAAO,UACZ,KAAM,EACN,SAAU,KAAK,IAAG,EAClB,wBAAyB,EACjC,EAII,GAAI,CAACD,GAAQA,IAAS,QAAUA,IAAS,YACvC,eAAQ,IAAI,oBAAoBD,CAAI,IAAIH,CAAE,kDAAkD,EACrF,KAIT,MAAMQ,EAAW,MAAM,KAAK,QAAQR,CAAE,EAChCS,EAAW,KAAK,YAAYN,EAAMH,CAAE,EAE1C,GAAIQ,EAEF,GAAIA,EAAS,MAAQH,EAAK,CAExB,MAAMK,EAAiB,MAAM,KAAK,MAAM,MAAMD,CAAQ,EAEtD,GAAIC,GAAkBP,IAAS,QAAS,CACtC,MAAMQ,EAAO,MAAMD,EAAe,KAAI,EAChCE,EAAcF,EAAe,QAAQ,IAAI,cAAc,EAG7D,GAAIE,IAAgB,cAAgBD,EAAK,KAAO,IAC9C,QAAQ,KAAK,kCAAkCR,CAAI,IAAIH,CAAE,KAAKY,CAAW,KAAKD,EAAK,IAAI,0BAA0B,EACjH,MAAM,KAAK,MAAM,OAAOF,CAAQ,MAGhC,gBAAQ,IAAI,WAAWN,CAAI,IAAIH,CAAE,iBAAiB,EAC3CQ,CAEX,KACE,gBAAQ,IAAI,WAAWL,CAAI,IAAIH,CAAE,iBAAiB,EAC3CQ,CAEX,MAEE,QAAQ,KAAK,WAAWL,CAAI,IAAIH,CAAE,yBAAyBQ,EAAS,GAAG,eAAeH,CAAG,oBAAoB,EAC7G,MAAM,KAAK,MAAM,OAAOI,CAAQ,EAKpC,QAAQ,IAAI,uBAAuBN,CAAI,IAAIH,CAAE,SAASI,CAAI,EAAE,EAG5D,MAAMS,EAAc,KAAK,WAAWT,CAAI,EACxC,QAAQ,IAAI,sBAAsBS,CAAW,EAAE,EAG/C,MAAMC,EAAe,MAAM,MAAMD,EAAa,CAAE,OAAQ,OAAQ,EAKhE,GAAIC,EAAa,SAAW,IAC1B,eAAQ,KAAK,WAAWX,CAAI,IAAIH,CAAE,6EAA6E,EACxG,CACL,GAAAA,EACA,KAAAG,EACA,KAAAC,EACA,IAAKC,GAAO,UACZ,KAAM,EACN,SAAU,KAAK,IAAG,EAClB,UAAW,EACnB,EAGI,MAAMU,EAAgB,SAASD,EAAa,QAAQ,IAAI,gBAAgB,GAAK,GAAG,EAC1EE,EAAcD,EAAgB,IAAM,KAAO,KAEjD,QAAQ,IAAI,uBAAuBA,EAAgB,KAAO,MAAM,QAAQ,CAAC,CAAC,OAAOC,EAAc,eAAiB,EAAE,EAAE,EAGpH,MAAMC,EAAWd,IAAS,QAAU,KAAK,gBAAgBC,CAAI,EAAIJ,EAMjE,IAAIkB,EACAC,EAEJ,GAAIH,EAAa,CAEf,QAAQ,IAAI,iCAAiCD,EAAgB,KAAO,MAAM,QAAQ,CAAC,CAAC,6BAA6B,EAGjH,KAAK,8BAA8BF,EAAaJ,EAAUM,EAAeE,EAAUjB,EAAIG,EAAMC,EAAMC,CAAG,EACnG,MAAMe,GAAO,QAAQ,KAAK,0CAA0CpB,CAAE,IAAKoB,CAAG,CAAC,EAGlF,MAAMC,EAAW,CACf,GAAArB,EACA,KAAAG,EACA,KAAAC,EACA,IAAKC,GAAO,UACZ,KAAMU,EACN,SAAU,KAAK,IAAG,EAClB,qBAAsB,EAC9B,EAEM,aAAM,KAAK,SAASM,CAAQ,EAE5B,QAAQ,IAAI,WAAWlB,CAAI,IAAIH,CAAE,+BAA+Be,CAAa,SAAS,EAE/EM,CACT,KAAO,CAEL,KAAK,uBAAuBJ,EAAU,EAAGF,CAAa,EAGtD,MAAMO,EAAW,MAAM,MAAMT,CAAW,EAKxC,GAAIS,EAAS,SAAW,IACtB,eAAQ,KAAK,WAAWnB,CAAI,IAAIH,CAAE,6EAA6E,EACxG,CACL,GAAAA,EACA,KAAAG,EACA,KAAAC,EACA,IAAKC,GAAO,UACZ,KAAM,EACN,SAAU,KAAK,IAAG,EAClB,UAAW,EACrB,EAGM,GAAI,CAACiB,EAAS,GACZ,MAAM,IAAI,MAAM,sBAAsBlB,CAAI,KAAKkB,EAAS,MAAM,EAAE,EAGlE,MAAMX,EAAO,MAAMW,EAAS,KAAI,EAC1BC,EAAc,MAAMZ,EAAK,YAAW,EAG1CO,EAAgBvC,EAAS,YAAY,KAAK4C,CAAW,EACjDlB,GAAOa,IAAkBb,IAK3B,QAAQ,KAAK,4BAA4BF,CAAI,IAAIH,CAAE,GAAG,EACtD,QAAQ,KAAK,uBAAuBK,CAAG,EAAE,EACzC,QAAQ,KAAK,uBAAuBa,CAAa,EAAE,EACnD,QAAQ,KAAK,uEAAuE,EAGpFA,EAAgBb,GAIlB,MAAM,KAAK,MAAM,IAAII,EAAU,IAAI,SAASE,EAAM,CAChD,QAAS,CACP,eAAgBW,EAAS,QAAQ,IAAI,cAAc,GAAK,2BACxD,iBAAkBX,EAAK,IACjC,CACA,CAAO,CAAC,EAEFQ,EAAWR,EAAK,KAChB,KAAK,uBAAuBM,EAAUE,EAAUJ,EAAe,EAAI,EACnE,QAAQ,IAAI,kBAAkBZ,CAAI,IAAIH,CAAE,KAAKmB,CAAQ,gBAAgBD,CAAa,GAAG,CACvF,CAGA,MAAMjB,EAAa,CACjB,GAAAD,EACA,KAAAG,EACA,KAAAC,EACA,IAAKc,EACL,KAAMC,EACN,SAAU,KAAK,IAAG,CACxB,EACI,aAAM,KAAK,SAASlB,CAAU,EAEvBA,CACT,CAMA,YAAYE,EAAMH,EAAIiB,EAAW,KAAM,CAErC,MAAO,GAAG3B,CAAI,UAAUa,CAAI,IADhBc,GAAYjB,CACW,EACrC,CAKA,MAAM,cAAcG,EAAMH,EAAI,CAC5B,MAAMS,EAAW,KAAK,YAAYN,EAAMH,CAAE,EACpCsB,EAAW,MAAM,KAAK,MAAM,MAAMb,CAAQ,EAEhD,OAAKa,EAGE,MAAMA,EAAS,KAAI,EAFjB,IAGX,CAKA,MAAM,kBAAkBnB,EAAMH,EAAI,CAChC,MAAMS,EAAW,KAAK,YAAYN,EAAMH,CAAE,EAC1C,OAAO,MAAM,KAAK,MAAM,MAAMS,CAAQ,CACxC,CAKA,MAAM,kBAAkBN,EAAMH,EAAI,CAChC,MAAMS,EAAW,KAAK,YAAYN,EAAMH,CAAE,EACpCsB,EAAW,MAAM,KAAK,MAAM,MAAMb,CAAQ,EAChD,OAAKa,EAGE,MAAMA,EAAS,KAAI,EAFjB,IAGX,CAUA,MAAM,gBAAgBE,EAAUC,EAAUC,EAASC,EAAM,CACvD,MAAMlB,EAAW,GAAGnB,CAAI,iBAAiBkC,CAAQ,IAAIC,CAAQ,IAAIC,CAAO,GAClEE,EAAQ,MAAM,OAAO,KAAK3C,CAAU,EAIpC4C,EAAU,qCAChB,IAAIC,EAAeH,EAGfA,EAAK,SAAS,QAAQ,EACxBG,EAAeH,EAAK,QAAQ,SAAU,SAAWE,CAAO,EAC/CF,EAAK,SAAS,QAAQ,EAC/BG,EAAeH,EAAK,QAAQ,SAAU,SAAWE,CAAO,EAGxDC,EAAeD,EAAUF,EAM3B,MAAMI,EAAc,sEACdC,EAAkB,CAAA,EACxBF,EAAeA,EAAa,QAAQC,EAAa,CAACE,EAAOhB,IAAa,CACpE,MAAMiB,EAAY,GAAG5C,CAAI,iBAAiB2B,CAAQ,GAClD,OAAAe,EAAgB,KAAK,CAAE,SAAAf,EAAU,YAAagB,CAAK,CAAE,EACrD,QAAQ,IAAI,+BAA+BhB,CAAQ,MAAMiB,CAAS,EAAE,EAC7DA,CACT,CAAC,EAKD,MAAMC,EAAY,0DACdL,EAAa,SAAS,SAAS,EACjCA,EAAeA,EAAa,QAAQ,UAAWK,EAAY,SAAS,EAC3DL,EAAa,SAAS,SAAS,IACxCA,EAAeA,EAAa,QAAQ,UAAWK,EAAY,SAAS,GAOtEL,EAAeA,EAAa,QAC1B,+CACA,iBAAiBxC,CAAI,MAC3B,EAEI,QAAQ,IAAI,+DAA+D,EAG3E,MAAM8C,EAAW,IAAI,IAAI3B,EAAU,OAAO,SAAS,MAAM,EAEnDa,EAAW,IAAI,SAASQ,EAAc,CAC1C,QAAS,CACP,eAAgB,2BAChB,8BAA+B,GACvC,CACA,CAAK,EAMD,GAJA,MAAMF,EAAM,IAAIQ,EAAUd,CAAQ,EAClC,QAAQ,IAAI,iCAAiCb,CAAQ,KAAKqB,EAAa,MAAM,SAAS,EAGlFE,EAAgB,OAAS,EAAG,CAE9B,MAAMK,EAAc,MAAM,OAAO,KADP,gBAC6B,EAEvD,MAAM,QAAQ,IAAIL,EAAgB,IAAI,MAAO,CAAE,SAAAf,EAAU,YAAAqB,KAAkB,CACzE,MAAMC,EAAY,GAAGjD,CAAI,iBAAiB2B,CAAQ,GAElD,GADiB,OAAMoB,EAAY,MAAME,CAAS,EAGlD,GAAI,CACF,MAAMC,EAAO,MAAM,MAAMF,CAAW,EACpC,GAAI,CAACE,EAAK,GAAI,CACZ,QAAQ,KAAK,4CAA4CvB,CAAQ,UAAUuB,EAAK,MAAM,GAAG,EACzF,MACF,CAEA,MAAMC,EAAMxB,EAAS,MAAM,GAAG,EAAE,IAAG,EAAG,YAAW,EAC3CL,EAAc,CAClB,GAAM,yBACN,IAAO,WACP,IAAO,WAAY,IAAO,WAC1B,KAAQ,YAAa,MAAS,aAC9B,IAAO,gCACP,IAAO,eACnB,EAAY6B,CAAG,GAAK,2BAGV,GAAIA,IAAQ,MAAO,CACjB,IAAIC,EAAU,MAAMF,EAAK,KAAI,EAC7B,MAAMG,EAAgB,CAAA,EAChBC,EAAe,4GACrBF,EAAUA,EAAQ,QAAQE,EAAc,CAACC,EAAQC,EAAOC,EAASC,KAC/DL,EAAc,KAAK,CAAE,SAAUK,EAAc,YAAaD,EAAS,EACnE,QAAQ,IAAI,oCAAoCC,CAAY,EAAE,EACvD,OAAOF,CAAK,GAAGxD,CAAI,iBAAiB,mBAAmB0D,CAAY,CAAC,GAAGF,CAAK,IACpF,EAED,MAAMT,EAAY,IAAIE,EAAW,IAAI,SAASG,EAAS,CACrD,QAAS,CAAE,eAAgB,UAAU,CACnD,CAAa,CAAC,EACF,QAAQ,IAAI,2BAA2BC,EAAc,MAAM,yBAAyB1B,CAAQ,EAAE,EAG9F,MAAM,QAAQ,IAAI0B,EAAc,IAAI,MAAO,CAAE,SAAUM,EAAU,YAAaC,KAAc,CAC1F,MAAMC,EAAU,GAAG7D,CAAI,iBAAiB,mBAAmB2D,CAAQ,CAAC,GAEpE,GADqB,OAAMZ,EAAY,MAAMc,CAAO,EAGpD,GAAI,CACF,MAAMC,EAAW,MAAM,MAAMF,CAAO,EACpC,GAAI,CAACE,EAAS,GAAI,CAChB,QAAQ,KAAK,iCAAiCH,CAAQ,UAAUG,EAAS,MAAM,GAAG,EAClF,MACF,CACA,MAAMC,EAAW,MAAMD,EAAS,KAAI,EAC9BE,EAAUL,EAAS,MAAM,GAAG,EAAE,IAAG,EAAG,YAAW,EAC/CM,EAAkB,CACtB,IAAO,WAAY,IAAO,WAC1B,KAAQ,YAAa,MAAS,aAC9B,IAAO,gCACP,IAAO,eACzB,EAAkBD,CAAO,GAAK,2BAEd,MAAMjB,EAAY,IAAIc,EAAS,IAAI,SAASE,EAAU,CACpD,QAAS,CAAE,eAAgBE,CAAe,CAC5D,CAAiB,CAAC,EACF,QAAQ,IAAI,wBAAwBN,CAAQ,KAAKM,CAAe,KAAKF,EAAS,IAAI,SAAS,CAC7F,OAASG,EAAS,CAChB,QAAQ,KAAK,iCAAiCP,CAAQ,GAAIO,CAAO,CACnE,CACF,CAAC,CAAC,CACJ,KAAO,CACL,MAAM7C,EAAO,MAAM6B,EAAK,KAAI,EAC5B,MAAMH,EAAY,IAAIE,EAAW,IAAI,SAAS5B,EAAM,CAClD,QAAS,CAAE,eAAgBC,CAAW,CACpD,CAAa,CAAC,EACF,QAAQ,IAAI,mCAAmCK,CAAQ,KAAKL,CAAW,KAAKD,EAAK,IAAI,SAAS,CAChG,CACF,OAAS8C,EAAO,CACd,QAAQ,KAAK,4CAA4CxC,CAAQ,GAAIwC,CAAK,CAC5E,CACF,CAAC,CAAC,CACJ,CAEA,OAAOhD,CACT,CAOA,aAAaiB,EAASF,EAAU,CAC9B,MAAMkC,EAAM,OAAOhC,CAAO,EACrB,KAAK,WAAW,IAAIgC,CAAG,GAC1B,KAAK,WAAW,IAAIA,EAAK,IAAI,GAAK,EAEpC,KAAK,WAAW,IAAIA,CAAG,EAAE,IAAI,OAAOlC,CAAQ,CAAC,CAC/C,CAOA,uBAAuBA,EAAU,CAC/B,MAAMmC,EAAM,OAAOnC,CAAQ,EACrBoC,EAAW,CAAA,EAEjB,SAAW,CAAClC,EAASmC,CAAO,IAAK,KAAK,WACpCA,EAAQ,OAAOF,CAAG,EACdE,EAAQ,OAAS,IACnB,KAAK,WAAW,OAAOnC,CAAO,EAC9BkC,EAAS,KAAKlC,CAAO,GAIzB,OAAIkC,EAAS,OAAS,GACpB,QAAQ,IAAI,WAAWA,EAAS,MAAM,sCAAsCpC,CAAQ,YAAaoC,CAAQ,EAEpGA,CACT,CAOA,kBAAkBlC,EAAS,CACzB,MAAMmC,EAAU,KAAK,WAAW,IAAI,OAAOnC,CAAO,CAAC,EACnD,OAAOmC,EAAUA,EAAQ,KAAO,EAAI,EACtC,CAOA,MAAM,8BAA8BhD,EAAaJ,EAAUM,EAAeE,EAAUjB,EAAIG,EAAMC,EAAMC,EAAK,OAEvG,IAAIyD,EAAkB,EAEtB,QAAQ,IAAI,wCAAwC7C,CAAQ,EAAE,EAC9D,KAAK,uBAAuBA,EAAU,EAAGF,CAAa,EAEtD,GAAI,CAEF,MAAMgD,EAAc,CAAA,EACpB,QAASC,EAAQ,EAAGA,EAAQjD,EAAeiD,GAAS,SAAY,CAC9D,MAAMvG,EAAM,KAAK,IAAIuG,EAAQ,SAAa,EAAGjD,EAAgB,CAAC,EAC9DgD,EAAY,KAAK,CAAE,MAAAC,EAAO,IAAAvG,EAAK,MAAOsG,EAAY,OAAQ,CAC5D,CAEA,QAAQ,IAAI,uBAAuBA,EAAY,MAAM,wBAAwB1E,CAAiB,cAAc,EAG5G,MAAM4E,EAAW,IAAI,IACrB,IAAIC,EAAiB,EAErB,MAAMC,EAAgB,MAAOC,GAAU,CACrC,MAAMC,EAAc,SAASD,EAAM,KAAK,IAAIA,EAAM,GAAG,GAErD,GAAI,CACF,MAAME,EAAgB,MAAM,MAAMzD,EAAa,CAC7C,QAAS,CAAE,MAASwD,CAAW,CAC3C,CAAW,EAED,GAAI,CAACC,EAAc,IAAMA,EAAc,SAAW,IAChD,MAAM,IAAI,MAAM,SAASF,EAAM,KAAK,YAAYE,EAAc,MAAM,EAAE,EAGxE,MAAMC,EAAY,MAAMD,EAAc,KAAI,EAC1CL,EAAS,IAAIG,EAAM,MAAOG,CAAS,EAEnCT,GAAmBS,EAAU,KAC7B,MAAMC,GAAaV,EAAkB/C,EAAiB,KAAK,QAAQ,CAAC,EACpE,eAAQ,IAAI,iBAAiBqD,EAAM,KAAK,IAAIL,EAAY,OAAS,CAAC,cAAcS,CAAQ,IAAI,EAC5F,KAAK,uBAAuBvD,EAAU6C,EAAiB/C,CAAa,EAE7DwD,CACT,OAASd,EAAO,CACd,cAAQ,MAAM,iBAAiBW,EAAM,KAAK,WAAYX,CAAK,EACrDA,CACR,CACF,EAGMgB,EAAe,SAAY,CAC/B,KAAOP,EAAiBH,EAAY,QAAQ,CAC1C,MAAMK,EAAQL,EAAYG,GAAgB,EAC1C,MAAMC,EAAcC,CAAK,CAC3B,CACF,EAGMM,EAAc,CAAA,EACpB,QAASpI,EAAI,EAAGA,EAAI+C,EAAmB/C,IACrCoI,EAAY,KAAKD,GAAc,EAGjC,MAAM,QAAQ,IAAIC,CAAW,EAG7B,MAAMC,EAAgB,CAAA,EACtB,QAASrI,EAAI,EAAGA,EAAIyH,EAAY,OAAQzH,IACtCqI,EAAc,KAAKV,EAAS,IAAI3H,CAAC,CAAC,EAIpC,MAAMqE,EAAO,IAAI,KAAKgE,CAAa,EAG7B/D,IAAcL,EAAAoE,EAAc,CAAC,IAAf,YAAApE,EAAkB,OAAQ,YAG9C,MAAM,KAAK,MAAM,IAAIE,EAAU,IAAI,SAASE,EAAM,CAChD,QAAS,CACP,eAAgBC,EAChB,iBAAkBD,EAAK,KACvB,gBAAiB,OAC3B,CACA,CAAO,CAAC,EAGF,MAAMU,EAAW,CACf,GAAArB,EACA,KAAAG,EACA,KAAAC,EACA,IAAKC,GAAO,aACZ,KAAMM,EAAK,KACX,SAAU,KAAK,IAAG,EAClB,qBAAsB,GACtB,OAAQ,EAChB,EAEM,MAAM,KAAK,SAASU,CAAQ,EAE5B,KAAK,uBAAuBJ,EAAU6C,EAAiB/C,EAAe,EAAI,EAE1E,QAAQ,IAAI,yCAAyCE,CAAQ,KAAKN,EAAK,IAAI,aAAagE,EAAc,MAAM,UAAU,EAGtH,OAAO,cAAc,IAAI,YAAY,eAAgB,CACnD,OAAQ,CAAE,SAAA1D,EAAU,GAAAjB,EAAI,KAAAG,EAAM,KAAMQ,EAAK,IAAI,CACrD,CAAO,CAAC,CACJ,OAAS8C,EAAO,CACd,QAAQ,MAAM,0CAA0CxC,CAAQ,IAAKwC,CAAK,EAC1E,KAAK,uBAAuBxC,EAAU6C,EAAiB/C,EAAe,GAAO,EAAI,CACnF,CACF,CAKA,uBAAuBE,EAAU2D,EAAQC,EAAOC,EAAW,GAAOrB,EAAQ,GAAO,CAC/E,MAAMxI,EAAQ,IAAI,YAAY,oBAAqB,CACjD,OAAQ,CACN,SAAAgG,EACA,OAAA2D,EACA,MAAAC,EACA,QAASA,EAAQ,EAAKD,EAASC,EAAS,IAAM,EAC9C,SAAAC,EACA,MAAArB,CACR,CACA,CAAK,EACD,OAAO,cAAcxI,CAAK,CAC5B,CAKA,MAAM,UAAW,CACf,aAAM,OAAO,OAAOgE,CAAU,EAC9B,KAAK,MAAQ,MAAM,OAAO,KAAKA,CAAU,EAElC,IAAI,QAAQ,CAACW,EAASC,IAAW,CAGtC,MAAMC,EAFK,KAAK,GAAG,YAAYV,EAAa,WAAW,EACtC,YAAYA,CAAW,EAClB,MAAK,EAC3BU,EAAQ,UAAY,IAAMF,EAAO,EACjCE,EAAQ,QAAU,IAAMD,EAAOC,EAAQ,KAAK,CAC9C,CAAC,CACH,CACF,CAEY,MAACiF,EAAe,IAAIxF,EClsB1ByF,EAAMC,EAAa,YAAY,EAG/B3F,EAAQ,OAAO,OAAW,KAC5B,OAAO,SAAS,SAAS,QAAQ,WAAY,EAAE,EAAE,QAAQ,MAAO,EAAE,GAAK,cAM3E,MAAM4F,UAA6BlK,CAAa,CAC9C,aAAc,CACZ,MAAK,EACL,KAAK,WAAa,KAClB,KAAK,WAAa,GAClB,KAAK,kBAAoB,KACzB,KAAK,kBAAoB,IAC3B,CAEA,MAAM,MAAO,CAgBX,GAdA,KAAK,kBAAoB,IAAI,QAAQ4E,GAAW,CAC9C,KAAK,kBAAoBA,CAC3B,CAAC,EAGD,UAAU,cAAc,iBAAiB,UAAY3E,GAAU,SACzDsF,EAAAtF,EAAM,OAAN,YAAAsF,EAAY,QAAS,aACvByE,EAAI,KAAK,mDAAmD,EAC5D,KAAK,WAAa,GAClB,KAAK,kBAAiB,EAE1B,CAAC,EAGG,kBAAmB,UAAW,CAChC,MAAMG,EAAe,MAAM,UAAU,cAAc,gBAAe,EAGlE,GAAIA,GAAgBA,EAAa,QAAUA,EAAa,OAAO,QAAU,YAAa,CACpFH,EAAI,KAAK,uDAAuD,EAChE,KAAK,WAAa,UAAU,cAAc,YAAcG,EAAa,OAGrE,KAAK,WAAW,YAAY,CAAE,KAAM,MAAM,CAAE,EAE5CH,EAAI,KAAK,oEAAoE,EAC7E,MACF,CAMA,GAHA,MAAM,UAAU,cAAc,MAC9B,KAAK,WAAa,UAAU,cAAc,WAEtC,CAAC,KAAK,WACR,MAAM,IAAI,MAAM,qCAAqC,EAIvD,KAAK,WAAW,YAAY,CAAE,KAAM,MAAM,CAAE,EAE5CA,EAAI,KAAK,oEAAoE,CAC/E,KACE,OAAM,IAAI,MAAM,8BAA8B,CAElD,CAQA,MAAM,QAAQ7E,EAAMH,EAAI,CAEjB,KAAK,aACRgF,EAAI,MAAM,4DAA4D7E,CAAI,IAAIH,CAAE,KAAK,EACrF,MAAM,KAAK,kBACXgF,EAAI,MAAM,+CAA+C,GAK3D,MAAM5C,EAAW,GAAG9C,CAAI,UAAUa,CAAI,IAAIH,CAAE,GAE5CgF,EAAI,MAAM,WAAW7E,CAAI,KAAKH,CAAE,gBAAgBoC,CAAQ,EAAE,EAC1D4C,EAAI,MAAM,0BAA0B,EAEpC,GAAI,CACFA,EAAI,MAAM,iBAAiB5C,CAAQ,MAAM,EACzC,MAAMd,EAAW,MAAM,MAAMc,CAAQ,EAGrC,GAFA4C,EAAI,MAAM,0BAA2B1D,EAAS,OAAQA,EAAS,UAAU,EAErE,CAACA,EAAS,GAAI,CAEhB,GADA0D,EAAI,MAAM,oBAAoB1D,EAAS,MAAM,mBAAmB,EAC5DA,EAAS,SAAW,IACtB,OAAO,KAET,MAAM,IAAI,MAAM,uBAAuBA,EAAS,MAAM,EAAE,CAC1D,CAEA0D,EAAI,MAAM,8BAA8B,EACxC,MAAMrE,EAAO,MAAMW,EAAS,KAAI,EAChC,OAAA0D,EAAI,MAAM,kBAAmBrE,EAAK,IAAI,EAC/BA,CACT,OAAS8C,EAAO,CACd,OAAAuB,EAAI,MAAM,qBAAsBvB,CAAK,EACrCuB,EAAI,MAAM,cAAevB,EAAM,IAAI,EACnCuB,EAAI,MAAM,iBAAkBvB,EAAM,OAAO,EAClC,IACT,CACF,CASA,MAAM,QAAQtD,EAAMH,EAAI,CAEjB,KAAK,YACR,MAAM,KAAK,kBAGb,MAAMoC,EAAW,GAAG9C,CAAI,UAAUa,CAAI,IAAIH,CAAE,GAE5C,GAAI,CAIF,OADiB,MAAM,MAAMoC,EAAU,CAAE,OAAQ,OAAQ,GACzC,EAClB,MAAgB,CACd,MAAO,EACT,CACF,CAOA,MAAM,gBAAgBgD,EAAS,CAC7B,GAAI,CAAC,KAAK,WACR,MAAM,IAAI,MAAM,8BAA8B,EAIhD,MAAMC,EAAO,MAAM,QAAQD,CAAO,EAC9B,CAAE,MAAOA,CAAO,EAChBA,EAEJ,OAAO,IAAI,QAAQ,CAACxF,EAASC,IAAW,CACtC,MAAMyF,EAAiB,IAAI,eAE3BA,EAAe,MAAM,UAAarK,GAAU,CAC1C,KAAM,CAAE,QAAAsK,EAAS,MAAA9B,EAAO,cAAA+B,EAAe,YAAAC,EAAa,YAAAC,CAAW,EAAKzK,EAAM,KACtEsK,GACFP,EAAI,KAAK,iCAAkCQ,EAAe,OAAO,EACjER,EAAI,KAAK,eAAgBS,EAAa,UAAWC,EAAa,QAAQ,EACtE9F,EAAO,GAEPC,EAAO,IAAI,MAAM4D,GAAS,gCAAgC,CAAC,CAE/D,EAEA,KAAK,WAAW,YACd,CACE,KAAM,iBACN,KAAA4B,CACV,EACQ,CAACC,EAAe,KAAK,CAC7B,CACI,CAAC,CACH,CAQA,MAAM,mBAAmBK,EAAUC,EAAQ,CACzC,GAAK,KAAK,WAEV,OAAO,IAAI,QAAShG,GAAY,CAC9B,MAAM0F,EAAiB,IAAI,eAC3BA,EAAe,MAAM,UAAarK,GAAU2E,EAAQ3E,EAAM,IAAI,EAC9D,KAAK,WAAW,YACd,CAAE,KAAM,sBAAuB,KAAM,CAAE,SAAA0K,EAAU,OAAAC,CAAM,CAAE,EACzD,CAACN,EAAe,KAAK,CAC7B,CACI,CAAC,CACH,CAQA,MAAM,SAASnF,EAAMH,EAAI,CACvB,OAAO,KAAK,QAAQG,EAAMH,CAAE,CAC9B,CAMA,MAAM,sBAAsB6F,EAAU,CAC/B,KAAK,YACV,KAAK,WAAW,YAAY,CAAE,KAAM,0BAA2B,KAAM,CAAE,SAAAA,CAAQ,EAAI,CACrF,CACF,CAOO,MAAMC,UAAmB9K,CAAa,CAC3C,aAAc,CACZ,MAAK,EACL,KAAK,QAAU,KACf,KAAK,YAAc,gBACrB,CAKA,MAAM,MAAO,SACX,GAAI,EAAE,kBAAmB,WACvB,MAAM,IAAI,MAAM,4DAA4D,EAG9EgK,EAAI,MAAM,kCAAkC,EAC5CA,EAAI,MAAM,eAAgB,UAAU,cAAc,UAAU,EAG5D,MAAMG,EAAe,MAAM,UAAU,cAAc,gBAAe,EAOlE,GANAH,EAAI,MAAM,iBAAkBG,CAAY,EACxCH,EAAI,MAAM,WAAYG,GAAA,YAAAA,EAAc,MAAM,EAC1CH,EAAI,MAAM,eAAgBG,GAAA,YAAAA,EAAc,UAAU,EAClDH,EAAI,MAAM,YAAaG,GAAA,YAAAA,EAAc,OAAO,EAGxCA,GAAgBA,EAAa,QAAU,CAACA,EAAa,YAAc,CAACA,EAAa,QAAS,CAC5FH,EAAI,KAAK,kDAAkD,EAC3DA,EAAI,MAAM,aAAcG,EAAa,OAAO,KAAK,EAG5C,UAAU,cAAc,aAC3BH,EAAI,MAAM,iDAAiD,EAC3D,MAAM,IAAI,QAAQpF,GAAW,WAAWA,EAAS,GAAG,CAAC,EACrDoF,EAAI,MAAM,2BAA4B,UAAU,cAAc,UAAU,GAI1E,KAAK,QAAU,IAAIE,EACnB,MAAM,KAAK,QAAQ,KAAI,EACvBF,EAAI,KAAK,0CAA0C,EACnD,MACF,CAGIG,IAAiBA,EAAa,YAAcA,EAAa,WAC3DH,EAAI,KAAK,4DAA4D,EACrEA,EAAI,MAAM,gBAAgBzE,EAAA4E,EAAa,aAAb,YAAA5E,EAAyB,KAAK,EACxDyE,EAAI,MAAM,aAAae,EAAAZ,EAAa,UAAb,YAAAY,EAAsB,KAAK,GAIpDf,EAAI,KAAK,uDAAuD,EAGhE,MAAMgB,EAAU,UAAU,cAAc,MAClCC,EAAU,IAAI,QAAQ,CAACC,EAAGrG,IAC9B,WAAW,IAAMA,EAAO,IAAI,MAAM,wCAAwC,CAAC,EAAG,GAAK,CACzF,EAEI,GAAI,CACF,MAAM,QAAQ,KAAK,CAACmG,EAASC,CAAO,CAAC,EACrCjB,EAAI,MAAM,uCAAuC,CACnD,OAASvB,EAAO,CACd,MAAAuB,EAAI,MAAM,8BAA+BvB,CAAK,EACxC,IAAI,MAAM,+CAA+C,CACjE,CAGA,MAAM,IAAI,QAAQ7D,GAAW,WAAWA,EAAS,GAAG,CAAC,EACrDoF,EAAI,MAAM,iCAAkC,UAAU,cAAc,UAAU,EAI9E,KAAK,QAAU,IAAIE,EACnB,MAAM,KAAK,QAAQ,KAAI,EACvBF,EAAI,KAAK,0CAA0C,CACrD,CAQA,MAAM,QAAQ7E,EAAMH,EAAI,CACtB,GAAI,CAAC,KAAK,QACR,MAAM,IAAI,MAAM,4BAA4B,EAE9C,OAAO,MAAM,KAAK,QAAQ,QAAQG,EAAMH,CAAE,CAC5C,CAQA,MAAM,QAAQG,EAAMH,EAAI,CACtB,GAAI,CAAC,KAAK,QACR,MAAM,IAAI,MAAM,4BAA4B,EAE9C,OAAO,MAAM,KAAK,QAAQ,QAAQG,EAAMH,CAAE,CAC5C,CAUA,MAAM,gBAAgBmG,EAAO,CAC3B,GAAI,CAAC,KAAK,QACR,MAAM,IAAI,MAAM,4BAA4B,EAE9C,OAAO,MAAM,KAAK,QAAQ,gBAAgBA,CAAK,CACjD,CAOA,MAAM,mBAAmBR,EAAUC,EAAQ,OACzC,IAAKrF,EAAA,KAAK,UAAL,MAAAA,EAAc,mBACnB,OAAO,MAAM,KAAK,QAAQ,mBAAmBoF,EAAUC,CAAM,CAC/D,CAQA,MAAM,SAASzF,EAAMH,EAAI,CACvB,OAAO,KAAK,QAAQG,EAAMH,CAAE,CAC9B,CAMA,MAAM,sBAAsB6F,EAAU,OACpC,IAAKtF,EAAA,KAAK,UAAL,MAAAA,EAAc,sBACnB,OAAO,MAAM,KAAK,QAAQ,sBAAsBsF,CAAQ,CAC1D,CAMA,gBAAiB,CACf,OAAO,KAAK,WACd,CAMA,sBAAuB,CACrB,OAAO,KAAK,cAAgB,gBAC9B,CAOA,MAAM,YAAYM,EAAO,CACvB,GAAI,CAAC,KAAK,QACR,MAAM,IAAI,MAAM,4BAA4B,EAG9C,OAAO,IAAI,QAAQ,CAACvG,EAASC,IAAW,CACtC,MAAMuG,EAAU,IAAI,eAEpBA,EAAQ,MAAM,UAAanL,GAAU,CACnC,KAAM,CAAE,QAAAsK,EAAS,MAAA9B,EAAO,QAAA4C,EAAS,MAAAxB,CAAK,EAAK5J,EAAM,KAC7CsK,EACF3F,EAAQ,CAAE,QAAAyG,EAAS,MAAAxB,EAAO,EAE1BhF,EAAO,IAAI,MAAM4D,GAAS,eAAe,CAAC,CAE9C,EAEA,UAAU,cAAc,WAAW,YACjC,CAAE,KAAM,eAAgB,KAAM,CAAE,MAAA0C,CAAK,CAAE,EACvC,CAACC,EAAQ,KAAK,CACtB,EAEM,WAAW,IAAMxG,EAAQ,CAAE,QAAS,EAAG,MAAOuG,EAAM,OAAQ,EAAG,GAAI,CACrE,CAAC,CACH,CAQA,MAAM,mBAAmBN,EAAU,OACjC,GAAI,CAACA,GAAYA,EAAS,SAAW,EAAG,MAAO,CAAE,OAAQ,EAAG,MAAO,CAAC,EAEpE,MAAMS,GAAa/F,EAAA,UAAU,gBAAV,YAAAA,EAAyB,WAC5C,OAAK+F,EAEE,IAAI,QAAS1G,GAAY,CAC9B,MAAMwG,EAAU,IAAI,eAEpBA,EAAQ,MAAM,UAAanL,GAAU,CACnC,KAAM,CAAE,QAAAsK,EAAS,OAAAgB,EAAQ,MAAA1B,CAAK,EAAK5J,EAAM,KACzC2E,EAAQ2F,EAAU,CAAE,OAAAgB,EAAQ,MAAA1B,CAAK,EAAK,CAAE,OAAQ,EAAG,MAAOgB,EAAS,MAAM,CAAE,CAC7E,EAEAS,EAAW,YACT,CAAE,KAAM,uBAAwB,KAAM,CAAE,SAAAT,CAAQ,CAAE,EAClD,CAACO,EAAQ,KAAK,CACtB,EAGM,WAAW,IAAMxG,EAAQ,CAAE,OAAQ,EAAG,MAAOiG,EAAS,OAAQ,EAAG,GAAI,CACvE,CAAC,EAjBuB,CAAE,OAAQ,EAAG,MAAOA,EAAS,MAAM,CAkB7D,CAMA,MAAM,qBAAsB,CAC1B,GAAI,CAAC,KAAK,QACR,MAAM,IAAI,MAAM,4BAA4B,EAG9C,OAAO,IAAI,QAASjG,GAAY,CAC9B,MAAMwG,EAAU,IAAI,eAEpBA,EAAQ,MAAM,UAAanL,GAAU,CACnC,KAAM,CAAE,QAAAsK,EAAS,SAAAf,CAAQ,EAAKvJ,EAAM,KACpC2E,EAAQ2F,EAAUf,EAAW,EAAE,CACjC,EAEA,UAAU,cAAc,WAAW,YACjC,CAAE,KAAM,uBAAuB,EAC/B,CAAC4B,EAAQ,KAAK,CACtB,EAGM,WAAW,IAAMxG,EAAQ,CAAA,CAAE,EAAG,GAAI,CACpC,CAAC,CACH,CACF","x_google_ignoreList":[1]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const d={DEBUG:0,INFO:1,WARNING:2,ERROR:3,NONE:4},f=[];class A{constructor(e,t=null){this.name=e,this.useGlobal=t===null,this.useGlobal||this.setLevel(t)}setLevel(e){this.useGlobal=!1,typeof e=="string"?this.level=d[e.toUpperCase()]??d.INFO:this.level=e}getEffectiveLevel(){return this.useGlobal?h.level:this.level}debug(...e){this.getEffectiveLevel()<=d.DEBUG&&console.log(`[${this.name}] DEBUG:`,...e),g("debug",this.name,e)}info(...e){this.getEffectiveLevel()<=d.INFO&&console.log(`[${this.name}]`,...e),g("info",this.name,e)}warn(...e){this.getEffectiveLevel()<=d.WARNING&&console.warn(`[${this.name}]`,...e),g("warning",this.name,e)}error(...e){this.getEffectiveLevel()<=d.ERROR&&console.error(`[${this.name}]`,...e),g("error",this.name,e)}log(e,...t){switch(e.toUpperCase()){case"DEBUG":return this.debug(...t);case"INFO":return this.info(...t);case"WARNING":case"WARN":return this.warn(...t);case"ERROR":return this.error(...t)}}}const h={level:d.WARNING,setGlobalLevel(i){typeof i=="string"?this.level=d[i.toUpperCase()]??d.INFO:this.level=i,console.log(`[Logger] Global log level set to: ${this.getLevelName(this.level)}`)},getLevelName(i){return Object.keys(d).find(e=>d[e]===i)||"UNKNOWN"}};let T=!1;if(typeof window<"u"){const e=new URLSearchParams(window.location.search).get("logLevel"),t=localStorage.getItem("xibo_log_level");e?(h.setGlobalLevel(e),T=!0):t?(h.setGlobalLevel(t),T=!0):h.setGlobalLevel("WARNING")}function b(i,e=null){return new A(i,e)}function v(i){h.setGlobalLevel(i),typeof window<"u"&&localStorage.setItem("xibo_log_level",i.toUpperCase())}function P(){return h.getLevelName(h.level)}function G(){return h.level<=d.DEBUG}function N(i){if(T||!i)return!1;const e=$(i);return h.setGlobalLevel(e),!0}function $(i){switch((i||"").toLowerCase()){case"debug":return"DEBUG";case"info":case"notice":case"audit":return"INFO";case"warning":return"WARNING";case"error":case"critical":case"alert":case"emergency":return"ERROR";default:return"INFO"}}function g(i,e,t){if(f.length!==0)for(const a of f)try{a({level:i,name:e,args:t})}catch{}}function D(i){f.push(i)}function K(i){const e=f.indexOf(i);e>=0&&f.splice(e,1)}var S={};const w="xibo_config",x="xibo-hw-backup",E=1;function L(){const i=typeof process<"u"&&S?S:{},e={cmsAddress:i.CMS_ADDRESS||i.CMS_URL||"",cmsKey:i.CMS_KEY||"",displayName:i.DISPLAY_NAME||"",hardwareKey:i.HARDWARE_KEY||"",xmrChannel:i.XMR_CHANNEL||""};return Object.values(e).some(a=>a!=="")?e:null}class C{constructor(){this.data=this.load(),this._fromEnv||this._restoreHardwareKeyFromBackup()}load(){const e=L();if(e)return this._fromEnv=!0,e;if(typeof localStorage>"u")return{cmsAddress:"",cmsKey:"",displayName:"",hardwareKey:"",xmrChannel:""};const t=localStorage.getItem(w);if(t)try{const s=JSON.parse(t);return!s.hardwareKey||s.hardwareKey.length<10?(console.error("[Config] CRITICAL: Invalid/missing hardwareKey in localStorage!"),s.hardwareKey=this.generateStableHardwareKey(),localStorage.setItem(w,JSON.stringify(s)),this._backupHardwareKey(s.hardwareKey)):console.log("[Config] ✓ Loaded existing hardwareKey:",s.hardwareKey),s}catch(s){console.error("[Config] Failed to parse config from localStorage:",s)}console.log("[Config] No config in localStorage - first time setup");const a={cmsAddress:"",cmsKey:"",displayName:"",hardwareKey:this.generateStableHardwareKey(),xmrChannel:this.generateXmrChannel()};return localStorage.setItem(w,JSON.stringify(a)),this._backupHardwareKey(a.hardwareKey),console.log("[Config] ✓ Saved new config to localStorage"),console.log("[Config] Hardware key will persist across reloads:",a.hardwareKey),a}_backupHardwareKey(e){try{const t=indexedDB.open(x,E);t.onupgradeneeded=()=>{const a=t.result;a.objectStoreNames.contains("keys")||a.createObjectStore("keys")},t.onsuccess=()=>{const a=t.result,s=a.transaction("keys","readwrite");s.objectStore("keys").put(e,"hardwareKey"),s.oncomplete=()=>{console.log("[Config] Hardware key backed up to IndexedDB"),a.close()}}}catch{}}async _restoreHardwareKeyFromBackup(){if(!(typeof indexedDB>"u"))try{const e=await new Promise((r,n)=>{const o=indexedDB.open(x,E);o.onupgradeneeded=()=>{const c=o.result;c.objectStoreNames.contains("keys")||c.createObjectStore("keys")},o.onsuccess=()=>r(o.result),o.onerror=()=>n(o.error)}),a=e.transaction("keys","readonly").objectStore("keys"),s=await new Promise(r=>{const n=a.get("hardwareKey");n.onsuccess=()=>r(n.result),n.onerror=()=>r(null)});e.close(),s&&s!==this.data.hardwareKey?(console.log("[Config] Restoring hardware key from IndexedDB backup:",s),console.log("[Config] (was:",this.data.hardwareKey,")"),this.data.hardwareKey=s,this.save()):!s&&this.data.hardwareKey&&this._backupHardwareKey(this.data.hardwareKey)}catch{}}save(){typeof localStorage<"u"&&localStorage.setItem(w,JSON.stringify(this.data))}isConfigured(){return!!(this.data.cmsAddress&&this.data.cmsKey&&this.data.displayName)}generateStableHardwareKey(){if(typeof crypto<"u"&&crypto.randomUUID){const s="pwa-"+crypto.randomUUID().replace(/-/g,"").substring(0,28);return console.log("[Config] Generated new UUID-based hardware key:",s),s}const t="pwa-"+Array.from({length:28},()=>Math.floor(Math.random()*16).toString(16)).join("");return console.log("[Config] Generated new random hardware key:",t),t}getCanvasFingerprint(){try{const e=document.createElement("canvas"),t=e.getContext("2d");return t?(t.textBaseline="top",t.font="14px Arial",t.fillStyle="#f60",t.fillRect(125,1,62,20),t.fillStyle="#069",t.fillText("Xibo Player",2,15),e.toDataURL()):"no-canvas"}catch{return"canvas-error"}}generateHardwareKey(){return this.generateStableHardwareKey()}generateXmrChannel(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,e=>{const t=Math.random()*16|0;return(e==="x"?t:t&3|8).toString(16)})}hash(e){let t=2166136261;for(let s=0;s<e.length;s++)t^=e.charCodeAt(s),t+=(t<<1)+(t<<4)+(t<<7)+(t<<8)+(t<<24);t=t>>>0;let a="";for(let s=0;s<4;s++){let r=t+s*1234567;for(let n=0;n<e.length;n++)r^=e.charCodeAt(n)+s,r+=(r<<1)+(r<<4)+(r<<7)+(r<<8)+(r<<24);r=r>>>0,a+=r.toString(16).padStart(8,"0")}return a.substring(0,32)}get cmsAddress(){return this.data.cmsAddress}set cmsAddress(e){this.data.cmsAddress=e,this.save()}get cmsKey(){return this.data.cmsKey}set cmsKey(e){this.data.cmsKey=e,this.save()}get displayName(){return this.data.displayName}set displayName(e){this.data.displayName=e,this.save()}get hardwareKey(){return this.data.hardwareKey||(console.error("[Config] CRITICAL: hardwareKey missing! Generating emergency key."),this.data.hardwareKey=this.generateStableHardwareKey(),this.save()),this.data.hardwareKey}get xmrChannel(){return this.data.xmrChannel}}const O=new C,k=b("FetchRetry");async function I(i,e={},t={}){const{maxRetries:a=3,baseDelayMs:s=1e3,maxDelayMs:r=3e4}=t;let n,o;for(let c=0;c<=a;c++){try{const l=await fetch(i,e);if(l.ok||l.status>=400&&l.status<500)return l;o=l,n=new Error(`HTTP ${l.status}: ${l.statusText}`),n.status=l.status}catch(l){n=l,o=null}if(c<a){const u=Math.min(s*Math.pow(2,c),r)*(.5+Math.random()*.5);k.debug(`Retry ${c+1}/${a} in ${Math.round(u)}ms:`,String(i).slice(0,80)),await new Promise(p=>setTimeout(p,u))}}if(o)return o;throw n}const y=b("CmsApi");class R{constructor({baseUrl:e,clientId:t,clientSecret:a,apiToken:s}={}){this.baseUrl=(e||"").replace(/\/+$/,""),this.clientId=t||null,this.clientSecret=a||null,this.accessToken=s||null,this.tokenExpiry=s?1/0:0}async authenticate(){y.info("Authenticating with CMS API...");const e=await fetch(`${this.baseUrl}/api/authorize/access_token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"client_credentials",client_id:this.clientId,client_secret:this.clientSecret})});if(!e.ok){const a=await e.text();throw new Error(`OAuth2 authentication failed (${e.status}): ${a}`)}const t=await e.json();return this.accessToken=t.access_token,this.tokenExpiry=Date.now()+(t.expires_in||3600)*1e3,y.info("Authenticated successfully, token expires in",t.expires_in,"s"),this.accessToken}async ensureToken(){if(!(this.accessToken&&Date.now()<this.tokenExpiry-6e4)){if(!this.clientId||!this.clientSecret){if(this.accessToken)return;throw new m("AUTH","/authorize",0,"No valid token and no OAuth2 credentials")}await this.authenticate()}}async request(e,t,a={}){var c;await this.ensureToken();const s=new URL(`${this.baseUrl}/api${t}`),r={method:e,headers:{Authorization:`Bearer ${this.accessToken}`}};if(e==="GET")for(const[l,u]of Object.entries(a))u!=null&&s.searchParams.set(l,String(u));else r.headers["Content-Type"]="application/x-www-form-urlencoded",r.body=new URLSearchParams(a);const n=await fetch(s,r);if(!n.ok){const l=await n.text();let u;try{const p=JSON.parse(l);u=((c=p.error)==null?void 0:c.message)||p.message||l}catch{u=l}throw new m(e,t,n.status,u)}return(n.headers.get("Content-Type")||"").includes("application/json")?n.json():null}get(e,t){return this.request("GET",e,t)}post(e,t){return this.request("POST",e,t)}put(e,t){return this.request("PUT",e,t)}del(e){return this.request("DELETE",e)}async findDisplay(e){y.info("Looking up display by hardwareKey:",e);const t=await this.request("GET","/display",{hardwareKey:e}),a=Array.isArray(t)?t:[];if(a.length===0)return y.info("No display found for hardwareKey:",e),null;const s=a[0];return y.info(`Found display: ${s.display} (ID: ${s.displayId}, licensed: ${s.licensed})`),s}async authorizeDisplay(e){y.info("Authorizing display:",e),await this.request("PUT",`/display/authorise/${e}`),y.info("Display authorized successfully")}async editDisplay(e,t){return y.info("Editing display:",e,t),this.request("PUT",`/display/${e}`,t)}async listDisplays(e={}){const t=await this.request("GET","/display",e);return Array.isArray(t)?t:[]}async requestScreenshot(e){await this.request("PUT",`/display/requestscreenshot/${e}`)}async getDisplayStatus(e){return this.request("GET",`/display/status/${e}`)}async requestMultipart(e,t,a){var o;await this.ensureToken();const s=`${this.baseUrl}/api${t}`,r=await fetch(s,{method:e,headers:{Authorization:`Bearer ${this.accessToken}`},body:a});if(!r.ok){const c=await r.text();let l;try{const u=JSON.parse(c);l=((o=u.error)==null?void 0:o.message)||u.message||c}catch{l=c}throw new Error(`CMS API ${e} ${t} failed (${r.status}): ${l}`)}return(r.headers.get("Content-Type")||"").includes("application/json")?r.json():null}async createLayout({name:e,resolutionId:t,description:a}){const s={name:e,resolutionId:t};return a&&(s.description=a),this.request("POST","/layout",s)}async listLayouts(e={}){const t=await this.request("GET","/layout",e);return Array.isArray(t)?t:[]}async getLayout(e){return this.request("GET",`/layout/${e}`)}async deleteLayout(e){await this.request("DELETE",`/layout/${e}`)}async publishLayout(e){await this.request("PUT",`/layout/publish/${e}`,{publishNow:1})}async checkoutLayout(e){return this.request("PUT",`/layout/checkout/${e}`)}async getDraftLayout(e){const t=await this.listLayouts({parentId:e});return t.length>0?t[0]:null}async editLayoutBackground(e,t){return this.request("PUT",`/layout/background/${e}`,t)}async addRegion(e,t){return this.request("POST",`/region/${e}`,t)}async editRegion(e,t){return this.request("PUT",`/region/${e}`,t)}async deleteRegion(e){await this.request("DELETE",`/region/${e}`)}async addWidget(e,t,a={}){const{templateId:s,displayOrder:r,...n}=a,o={};s!==void 0&&(o.templateId=s),r!==void 0&&(o.displayOrder=r);const c=await this.request("POST",`/playlist/widget/${e}/${t}`,o);return Object.keys(n).length>0?(n.duration!==void 0&&n.useDuration===void 0&&(n.useDuration=1),this.request("PUT",`/playlist/widget/${c.widgetId}`,n)):c}async editWidget(e,t){return this.request("PUT",`/playlist/widget/${e}`,t)}async deleteWidget(e){await this.request("DELETE",`/playlist/widget/${e}`)}async uploadMedia(e){return this.requestMultipart("POST","/library",e)}async listMedia(e={}){const t=await this.request("GET","/library",e);return Array.isArray(t)?t:[]}async getMedia(e){return this.request("GET",`/library/${e}`)}async deleteMedia(e){await this.request("DELETE",`/library/${e}`)}async createCampaign(e){return this.request("POST","/campaign",{name:e})}async listCampaigns(e={}){const t=await this.request("GET","/campaign",e);return Array.isArray(t)?t:[]}async deleteCampaign(e){await this.request("DELETE",`/campaign/${e}`)}async assignLayoutToCampaign(e,t,a){const s={layoutId:t};a!==void 0&&(s.displayOrder=a),await this.request("POST",`/campaign/layout/assign/${e}`,s)}async createSchedule(e){const t={...e};Array.isArray(t.displayGroupIds)&&delete t.displayGroupIds,await this.ensureToken();const a=`${this.baseUrl}/api/schedule`,s=new URLSearchParams;for(const[o,c]of Object.entries(t))c!=null&&s.set(o,String(c));if(Array.isArray(e.displayGroupIds))for(const o of e.displayGroupIds)s.append("displayGroupIds[]",String(o));const r=await fetch(a,{method:"POST",headers:{Authorization:`Bearer ${this.accessToken}`,"Content-Type":"application/x-www-form-urlencoded"},body:s});if(!r.ok){const o=await r.text();throw new Error(`CMS API POST /schedule failed (${r.status}): ${o}`)}return(r.headers.get("Content-Type")||"").includes("application/json")?r.json():null}async deleteSchedule(e){await this.request("DELETE",`/schedule/${e}`)}async listSchedules(e={}){const t=await this.request("GET","/schedule/data/events",e);return Array.isArray(t)?t:(t==null?void 0:t.events)||[]}async listDisplayGroups(e={}){const t=await this.request("GET","/displaygroup",e);return Array.isArray(t)?t:[]}async createDisplayGroup(e,t){const a={displayGroup:e};return t&&(a.description=t),this.request("POST","/displaygroup",a)}async deleteDisplayGroup(e){await this.request("DELETE",`/displaygroup/${e}`)}async assignDisplayToGroup(e,t){await this.ensureToken();const a=`${this.baseUrl}/api/displaygroup/${e}/display/assign`,s=new URLSearchParams;s.append("displayId[]",String(t));const r=await fetch(a,{method:"POST",headers:{Authorization:`Bearer ${this.accessToken}`,"Content-Type":"application/x-www-form-urlencoded"},body:s});if(!r.ok){const n=await r.text();throw new Error(`CMS API assign display to group failed (${r.status}): ${n}`)}}async unassignDisplayFromGroup(e,t){await this.ensureToken();const a=`${this.baseUrl}/api/displaygroup/${e}/display/unassign`,s=new URLSearchParams;s.append("displayId[]",String(t));const r=await fetch(a,{method:"POST",headers:{Authorization:`Bearer ${this.accessToken}`,"Content-Type":"application/x-www-form-urlencoded"},body:s});if(!r.ok){const n=await r.text();throw new Error(`CMS API unassign display from group failed (${r.status}): ${n}`)}}async listResolutions(){const e=await this.request("GET","/resolution");return Array.isArray(e)?e:[]}async listTemplates(e={}){const t=await this.request("GET","/template",e);return Array.isArray(t)?t:[]}async assignMediaToPlaylist(e,t){const a=Array.isArray(t)?t:[t];await this.ensureToken();const s=`${this.baseUrl}/api/playlist/library/assign/${e}`,r=new URLSearchParams;for(const c of a)r.append("media[]",String(c));const n=await fetch(s,{method:"POST",headers:{Authorization:`Bearer ${this.accessToken}`,"Content-Type":"application/x-www-form-urlencoded"},body:r});if(!n.ok){const c=await n.text();throw new m("POST",`/playlist/library/assign/${e}`,n.status,c)}return(n.headers.get("Content-Type")||"").includes("application/json")?n.json():null}async editLayout(e,t){return this.request("PUT",`/layout/${e}`,t)}}class m extends Error{constructor(e,t,a,s){super(`CMS API ${e} ${t} → ${a}: ${s}`),this.name="CmsApiError",this.method=e,this.path=t,this.status=a,this.detail=s}}export{R as C,d as L,N as a,O as b,b as c,m as d,I as f,P as g,G as i,$ as m,D as r,v as s,K as u};
|
|
2
|
+
//# sourceMappingURL=cms-api-Ce7EVg5h.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cms-api-Ce7EVg5h.js","sources":["../../../xiboplayer/packages/utils/src/logger.js","../../../xiboplayer/packages/utils/src/config.js","../../../xiboplayer/packages/utils/src/fetch-retry.js","../../../xiboplayer/packages/utils/src/cms-api.js"],"sourcesContent":["/**\n * Configurable Logger for Xibo Players\n *\n * Supports log levels: DEBUG, INFO, WARNING, ERROR, NONE\n *\n * Level precedence (highest wins):\n * 1. URL param ?logLevel=DEBUG\n * 2. localStorage xibo_log_level\n * 3. CMS setting via RegisterDisplay (call applyCmsLogLevel())\n * 4. Default: WARNING (production-safe)\n *\n * For development, pass ?logLevel=DEBUG in the URL.\n * Electron's --dev flag does this automatically.\n *\n * Loggers created without an explicit level are REACTIVE — they follow\n * the global level at call time, so setLogLevel() affects all of them.\n */\n\nconst LOG_LEVELS = {\n DEBUG: 0,\n INFO: 1,\n WARNING: 2,\n ERROR: 3,\n NONE: 4\n};\n\n// Log sink system — external consumers (e.g., LogReporter) can intercept all log output\nconst logSinks = [];\n\nclass Logger {\n /**\n * @param {string} name - Logger name (shown in prefix)\n * @param {string|null} level - Explicit level string, or null to follow global\n */\n constructor(name, level = null) {\n this.name = name;\n this.useGlobal = (level === null);\n if (!this.useGlobal) {\n this.setLevel(level);\n }\n }\n\n setLevel(level) {\n this.useGlobal = false;\n if (typeof level === 'string') {\n this.level = LOG_LEVELS[level.toUpperCase()] ?? LOG_LEVELS.INFO;\n } else {\n this.level = level;\n }\n }\n\n /** Effective level: own override or global */\n getEffectiveLevel() {\n return this.useGlobal ? globalConfig.level : this.level;\n }\n\n debug(...args) {\n if (this.getEffectiveLevel() <= LOG_LEVELS.DEBUG) {\n console.log(`[${this.name}] DEBUG:`, ...args);\n }\n _dispatchToSinks('debug', this.name, args);\n }\n\n info(...args) {\n if (this.getEffectiveLevel() <= LOG_LEVELS.INFO) {\n console.log(`[${this.name}]`, ...args);\n }\n _dispatchToSinks('info', this.name, args);\n }\n\n warn(...args) {\n if (this.getEffectiveLevel() <= LOG_LEVELS.WARNING) {\n console.warn(`[${this.name}]`, ...args);\n }\n _dispatchToSinks('warning', this.name, args);\n }\n\n error(...args) {\n if (this.getEffectiveLevel() <= LOG_LEVELS.ERROR) {\n console.error(`[${this.name}]`, ...args);\n }\n _dispatchToSinks('error', this.name, args);\n }\n\n // Convenience method for conditional logging\n log(level, ...args) {\n switch (level.toUpperCase()) {\n case 'DEBUG': return this.debug(...args);\n case 'INFO': return this.info(...args);\n case 'WARNING':\n case 'WARN': return this.warn(...args);\n case 'ERROR': return this.error(...args);\n }\n }\n}\n\n// Global log level configuration\nconst globalConfig = {\n level: LOG_LEVELS.WARNING, // Default: WARNING (production-safe)\n\n setGlobalLevel(level) {\n if (typeof level === 'string') {\n this.level = LOG_LEVELS[level.toUpperCase()] ?? LOG_LEVELS.INFO;\n } else {\n this.level = level;\n }\n\n console.log(`[Logger] Global log level set to: ${this.getLevelName(this.level)}`);\n },\n\n getLevelName(level) {\n return Object.keys(LOG_LEVELS).find(key => LOG_LEVELS[key] === level) || 'UNKNOWN';\n }\n};\n\n// Track whether the level was set by a local override (URL param / localStorage)\nlet hasLocalOverride = false;\n\n// Set global level from environment or localStorage\n// Default: WARNING (production-safe). Use ?logLevel=DEBUG for development,\n// or let the CMS override via applyCmsLogLevel().\nif (typeof window !== 'undefined') {\n const urlParams = new URLSearchParams(window.location.search);\n const urlLevel = urlParams.get('logLevel');\n const storageLevel = localStorage.getItem('xibo_log_level');\n\n if (urlLevel) {\n globalConfig.setGlobalLevel(urlLevel);\n hasLocalOverride = true;\n } else if (storageLevel) {\n globalConfig.setGlobalLevel(storageLevel);\n hasLocalOverride = true;\n } else {\n globalConfig.setGlobalLevel('WARNING');\n }\n}\n\n// Factory function — loggers follow global level by default (reactive)\nexport function createLogger(name, level = null) {\n return new Logger(name, level);\n}\n\n// Set global log level (and persist to localStorage)\nexport function setLogLevel(level) {\n globalConfig.setGlobalLevel(level);\n\n // Save to localStorage\n if (typeof window !== 'undefined') {\n localStorage.setItem('xibo_log_level', level.toUpperCase());\n }\n}\n\n// Get current log level name\nexport function getLogLevel() {\n return globalConfig.getLevelName(globalConfig.level);\n}\n\n/**\n * Returns true when the effective global level is DEBUG.\n * Use this for conditional debug features (video controls, overlays, etc.)\n */\nexport function isDebug() {\n return globalConfig.level <= LOG_LEVELS.DEBUG;\n}\n\n/**\n * Apply CMS logLevel setting — only if no local override (URL/localStorage) exists.\n * @param {string} cmsLevel - CMS level string: 'error', 'audit', 'info', 'debug'\n * @returns {boolean} true if the level was applied\n */\nexport function applyCmsLogLevel(cmsLevel) {\n if (hasLocalOverride) return false;\n if (!cmsLevel) return false;\n\n const mapped = mapCmsLogLevel(cmsLevel);\n globalConfig.setGlobalLevel(mapped);\n return true;\n}\n\n/**\n * Map CMS logLevel strings to internal level names.\n * CMS uses: 'emergency','alert','critical','error','warning','notice','info','debug','audit'\n * We collapse them to our 4 levels.\n */\nexport function mapCmsLogLevel(cmsLevel) {\n switch ((cmsLevel || '').toLowerCase()) {\n case 'debug':\n return 'DEBUG';\n case 'info':\n case 'notice':\n case 'audit':\n return 'INFO';\n case 'warning':\n return 'WARNING';\n case 'error':\n case 'critical':\n case 'alert':\n case 'emergency':\n return 'ERROR';\n default:\n return 'INFO';\n }\n}\n\n/**\n * Dispatch log entry to all registered sinks.\n * Sinks receive { level, name, args } and should not throw.\n * @private\n */\nfunction _dispatchToSinks(level, name, args) {\n if (logSinks.length === 0) return;\n for (const fn of logSinks) {\n try {\n fn({ level, name, args });\n } catch (_) {\n // Sink errors must never break logging\n }\n }\n}\n\n/**\n * Register a log sink — receives all log output regardless of level filtering.\n * @param {function} fn - Callback: ({ level, name, args }) => void\n */\nexport function registerLogSink(fn) {\n logSinks.push(fn);\n}\n\n/**\n * Unregister a previously registered log sink.\n * @param {function} fn - The same function reference passed to registerLogSink\n */\nexport function unregisterLogSink(fn) {\n const idx = logSinks.indexOf(fn);\n if (idx >= 0) logSinks.splice(idx, 1);\n}\n\nexport { LOG_LEVELS };\n","/**\n * Configuration management with priority: env vars → localStorage → defaults\n *\n * In Node.js (tests, CLI): environment variables are the only source.\n * In browser (PWA player): localStorage is primary, env vars override if set.\n */\n\nconst STORAGE_KEY = 'xibo_config';\nconst HW_DB_NAME = 'xibo-hw-backup';\nconst HW_DB_VERSION = 1;\n\n/**\n * Check for environment variable config (highest priority).\n * Env vars: CMS_ADDRESS, CMS_KEY, DISPLAY_NAME, HARDWARE_KEY, XMR_CHANNEL\n * Returns config object if any env vars are set, null otherwise.\n */\nfunction loadFromEnv() {\n // Check if process.env is available (Node.js or bundler injection)\n const env = typeof process !== 'undefined' && process.env ? process.env : {};\n\n const envConfig = {\n cmsAddress: env.CMS_ADDRESS || env.CMS_URL || '',\n cmsKey: env.CMS_KEY || '',\n displayName: env.DISPLAY_NAME || '',\n hardwareKey: env.HARDWARE_KEY || '',\n xmrChannel: env.XMR_CHANNEL || '',\n };\n\n // Return env config if any value is set\n const hasEnvValues = Object.values(envConfig).some(v => v !== '');\n return hasEnvValues ? envConfig : null;\n}\n\nexport class Config {\n constructor() {\n this.data = this.load();\n // Async: try to restore hardware key from IndexedDB if localStorage lost it\n // (only when not running from env vars)\n if (!this._fromEnv) {\n this._restoreHardwareKeyFromBackup();\n }\n }\n\n load() {\n // Priority 1: Environment variables (Node.js, tests, CI)\n const envConfig = loadFromEnv();\n if (envConfig) {\n this._fromEnv = true;\n return envConfig;\n }\n\n // Priority 2: localStorage (browser)\n if (typeof localStorage === 'undefined') {\n return { cmsAddress: '', cmsKey: '', displayName: '', hardwareKey: '', xmrChannel: '' };\n }\n\n // Try to load from localStorage\n const json = localStorage.getItem(STORAGE_KEY);\n\n if (json) {\n try {\n const config = JSON.parse(json);\n\n // CRITICAL: Hardware key must persist\n if (!config.hardwareKey || config.hardwareKey.length < 10) {\n console.error('[Config] CRITICAL: Invalid/missing hardwareKey in localStorage!');\n config.hardwareKey = this.generateStableHardwareKey();\n localStorage.setItem(STORAGE_KEY, JSON.stringify(config));\n this._backupHardwareKey(config.hardwareKey);\n } else {\n console.log('[Config] ✓ Loaded existing hardwareKey:', config.hardwareKey);\n }\n\n return config;\n } catch (e) {\n console.error('[Config] Failed to parse config from localStorage:', e);\n // Fall through to create new config\n }\n }\n\n // No config in localStorage - first time setup\n console.log('[Config] No config in localStorage - first time setup');\n\n const newConfig = {\n cmsAddress: '',\n cmsKey: '',\n displayName: '',\n hardwareKey: this.generateStableHardwareKey(),\n xmrChannel: this.generateXmrChannel()\n };\n\n // Save immediately\n localStorage.setItem(STORAGE_KEY, JSON.stringify(newConfig));\n this._backupHardwareKey(newConfig.hardwareKey);\n console.log('[Config] ✓ Saved new config to localStorage');\n console.log('[Config] Hardware key will persist across reloads:', newConfig.hardwareKey);\n\n return newConfig;\n }\n\n /**\n * Backup hardware key to IndexedDB (more persistent than localStorage).\n * IndexedDB survives \"Clear site data\" in some browsers where localStorage doesn't.\n */\n _backupHardwareKey(key) {\n try {\n const req = indexedDB.open(HW_DB_NAME, HW_DB_VERSION);\n req.onupgradeneeded = () => {\n const db = req.result;\n if (!db.objectStoreNames.contains('keys')) {\n db.createObjectStore('keys');\n }\n };\n req.onsuccess = () => {\n const db = req.result;\n const tx = db.transaction('keys', 'readwrite');\n tx.objectStore('keys').put(key, 'hardwareKey');\n tx.oncomplete = () => {\n console.log('[Config] Hardware key backed up to IndexedDB');\n db.close();\n };\n };\n } catch (e) {\n // IndexedDB not available — localStorage-only mode\n }\n }\n\n /**\n * Restore hardware key from IndexedDB if localStorage was cleared.\n * Runs async after construction — if a backed-up key is found and\n * differs from the current one, it restores the original key.\n */\n async _restoreHardwareKeyFromBackup() {\n if (typeof indexedDB === 'undefined') return;\n try {\n const db = await new Promise((resolve, reject) => {\n const req = indexedDB.open(HW_DB_NAME, HW_DB_VERSION);\n req.onupgradeneeded = () => {\n const db = req.result;\n if (!db.objectStoreNames.contains('keys')) {\n db.createObjectStore('keys');\n }\n };\n req.onsuccess = () => resolve(req.result);\n req.onerror = () => reject(req.error);\n });\n\n const tx = db.transaction('keys', 'readonly');\n const store = tx.objectStore('keys');\n const backedUpKey = await new Promise((resolve) => {\n const req = store.get('hardwareKey');\n req.onsuccess = () => resolve(req.result);\n req.onerror = () => resolve(null);\n });\n db.close();\n\n if (backedUpKey && backedUpKey !== this.data.hardwareKey) {\n console.log('[Config] Restoring hardware key from IndexedDB backup:', backedUpKey);\n console.log('[Config] (was:', this.data.hardwareKey, ')');\n this.data.hardwareKey = backedUpKey;\n this.save();\n } else if (!backedUpKey && this.data.hardwareKey) {\n // No backup yet — save current key as backup\n this._backupHardwareKey(this.data.hardwareKey);\n }\n } catch (e) {\n // IndexedDB not available — that's fine\n }\n }\n\n save() {\n if (typeof localStorage !== 'undefined') {\n localStorage.setItem(STORAGE_KEY, JSON.stringify(this.data));\n }\n }\n\n isConfigured() {\n return !!(this.data.cmsAddress && this.data.cmsKey && this.data.displayName);\n }\n\n generateStableHardwareKey() {\n // Generate a stable UUID-based hardware key\n // CRITICAL: This is generated ONCE and saved to localStorage\n // It NEVER changes unless localStorage is cleared manually\n\n // Use crypto.randomUUID if available (best randomness)\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n const uuid = crypto.randomUUID().replace(/-/g, ''); // Remove dashes\n const hardwareKey = 'pwa-' + uuid.substring(0, 28);\n console.log('[Config] Generated new UUID-based hardware key:', hardwareKey);\n return hardwareKey;\n }\n\n // Fallback: Generate random hex string\n const randomHex = Array.from({ length: 28 }, () =>\n Math.floor(Math.random() * 16).toString(16)\n ).join('');\n\n const hardwareKey = 'pwa-' + randomHex;\n console.log('[Config] Generated new random hardware key:', hardwareKey);\n return hardwareKey;\n }\n\n getCanvasFingerprint() {\n // Generate stable canvas fingerprint (same for same GPU/driver)\n try {\n const canvas = document.createElement('canvas');\n const ctx = canvas.getContext('2d');\n if (!ctx) return 'no-canvas';\n\n // Draw test pattern (same rendering = same device)\n ctx.textBaseline = 'top';\n ctx.font = '14px Arial';\n ctx.fillStyle = '#f60';\n ctx.fillRect(125, 1, 62, 20);\n ctx.fillStyle = '#069';\n ctx.fillText('Xibo Player', 2, 15);\n\n return canvas.toDataURL();\n } catch (e) {\n return 'canvas-error';\n }\n }\n\n generateHardwareKey() {\n // For backwards compatibility\n return this.generateStableHardwareKey();\n }\n\n generateXmrChannel() {\n // Generate UUID for XMR channel\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = Math.random() * 16 | 0;\n const v = c === 'x' ? r : (r & 0x3 | 0x8);\n return v.toString(16);\n });\n }\n\n hash(str) {\n // FNV-1a hash algorithm (better distribution than simple hash)\n // Produces high-entropy 32-character hex string\n let hash = 2166136261; // FNV offset basis\n\n for (let i = 0; i < str.length; i++) {\n hash ^= str.charCodeAt(i);\n hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);\n }\n\n // Convert to unsigned 32-bit integer\n hash = hash >>> 0;\n\n // Extend to 32 characters by hashing multiple times with different seeds\n let result = '';\n for (let round = 0; round < 4; round++) {\n let roundHash = hash + round * 1234567;\n for (let i = 0; i < str.length; i++) {\n roundHash ^= str.charCodeAt(i) + round;\n roundHash += (roundHash << 1) + (roundHash << 4) + (roundHash << 7) + (roundHash << 8) + (roundHash << 24);\n }\n roundHash = roundHash >>> 0;\n result += roundHash.toString(16).padStart(8, '0');\n }\n\n return result.substring(0, 32);\n }\n\n get cmsAddress() { return this.data.cmsAddress; }\n set cmsAddress(val) { this.data.cmsAddress = val; this.save(); }\n\n get cmsKey() { return this.data.cmsKey; }\n set cmsKey(val) { this.data.cmsKey = val; this.save(); }\n\n get displayName() { return this.data.displayName; }\n set displayName(val) { this.data.displayName = val; this.save(); }\n\n get hardwareKey() {\n // CRITICAL: Ensure hardware key never becomes undefined\n if (!this.data.hardwareKey) {\n console.error('[Config] CRITICAL: hardwareKey missing! Generating emergency key.');\n this.data.hardwareKey = this.generateStableHardwareKey();\n this.save();\n }\n return this.data.hardwareKey;\n }\n get xmrChannel() { return this.data.xmrChannel; }\n}\n\nexport const config = new Config();\n","/**\n * Fetch with retry and exponential backoff\n *\n * Wraps native fetch() with configurable retry logic for transient failures.\n * Only retries on network errors and 5xx server errors (not 4xx client errors).\n * On final attempt, returns the response as-is so the caller can handle errors.\n */\n\nimport { createLogger } from './logger.js';\n\nconst log = createLogger('FetchRetry');\n\n/**\n * Fetch with automatic retry on failure\n * @param {string|URL} url - URL to fetch\n * @param {RequestInit} [options] - Fetch options\n * @param {Object} [retryOptions] - Retry configuration\n * @param {number} [retryOptions.maxRetries=3] - Maximum retry attempts\n * @param {number} [retryOptions.baseDelayMs=1000] - Base delay between retries (doubles each time)\n * @param {number} [retryOptions.maxDelayMs=30000] - Maximum delay between retries\n * @returns {Promise<Response>}\n */\nexport async function fetchWithRetry(url, options = {}, retryOptions = {}) {\n const { maxRetries = 3, baseDelayMs = 1000, maxDelayMs = 30000 } = retryOptions;\n\n let lastError;\n let lastResponse;\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n const response = await fetch(url, options);\n\n // Don't retry client errors (4xx) — they won't change with retries\n if (response.ok || (response.status >= 400 && response.status < 500)) {\n return response;\n }\n\n // Server error (5xx) — retryable, but return on last attempt\n lastResponse = response;\n lastError = new Error(`HTTP ${response.status}: ${response.statusText}`);\n lastError.status = response.status;\n } catch (error) {\n // Network error — retryable\n lastError = error;\n lastResponse = null;\n }\n\n if (attempt < maxRetries) {\n const delay = Math.min(baseDelayMs * Math.pow(2, attempt), maxDelayMs);\n const jitter = delay * (0.5 + Math.random() * 0.5); // 50-100% of delay\n log.debug(`Retry ${attempt + 1}/${maxRetries} in ${Math.round(jitter)}ms:`, String(url).slice(0, 80));\n await new Promise(resolve => setTimeout(resolve, jitter));\n }\n }\n\n // On exhausted retries: return response if we have one (let caller handle),\n // throw if we only have network errors\n if (lastResponse) {\n return lastResponse;\n }\n throw lastError;\n}\n","/**\n * CMS API Client — OAuth2-authenticated REST client for Xibo CMS\n *\n * Full CRUD client for all Xibo CMS REST API entities: displays, layouts,\n * regions, widgets, media, campaigns, schedules, display groups, resolutions.\n * Implements OAuth2 client_credentials flow (machine-to-machine).\n *\n * Usage:\n * const api = new CmsApiClient({ baseUrl: 'https://cms.example.com', clientId, clientSecret });\n * await api.authenticate();\n * const layout = await api.createLayout({ name: 'Test', resolutionId: 9 });\n * const region = await api.addRegion(layout.layoutId, { width: 1920, height: 1080 });\n * await api.addWidget('text', region.playlists[0].playlistId, { text: 'Hello' });\n * await api.publishLayout(layout.layoutId);\n */\n\nimport { createLogger } from './logger.js';\n\nconst log = createLogger('CmsApi');\n\nexport class CmsApiClient {\n /**\n * @param {Object} options\n * @param {string} options.baseUrl - CMS base URL (e.g. https://cms.example.com)\n * @param {string} [options.clientId] - OAuth2 application client ID\n * @param {string} [options.clientSecret] - OAuth2 application client secret\n * @param {string} [options.apiToken] - Pre-configured bearer token (skips OAuth2 flow)\n */\n constructor({ baseUrl, clientId, clientSecret, apiToken } = {}) {\n this.baseUrl = (baseUrl || '').replace(/\\/+$/, '');\n this.clientId = clientId || null;\n this.clientSecret = clientSecret || null;\n this.accessToken = apiToken || null;\n this.tokenExpiry = apiToken ? Infinity : 0;\n }\n\n // ── OAuth2 Token Management ─────────────────────────────────────\n\n /**\n * Authenticate using client_credentials grant\n * @returns {Promise<string>} Access token\n */\n async authenticate() {\n log.info('Authenticating with CMS API...');\n\n const response = await fetch(`${this.baseUrl}/api/authorize/access_token`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n grant_type: 'client_credentials',\n client_id: this.clientId,\n client_secret: this.clientSecret\n })\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`OAuth2 authentication failed (${response.status}): ${text}`);\n }\n\n const data = await response.json();\n this.accessToken = data.access_token;\n this.tokenExpiry = Date.now() + (data.expires_in || 3600) * 1000;\n\n log.info('Authenticated successfully, token expires in', data.expires_in, 's');\n return this.accessToken;\n }\n\n /**\n * Ensure we have a valid token (auto-refresh if expired)\n */\n async ensureToken() {\n if (this.accessToken && Date.now() < this.tokenExpiry - 60000) return;\n if (!this.clientId || !this.clientSecret) {\n if (this.accessToken) return; // apiToken with no expiry\n throw new CmsApiError('AUTH', '/authorize', 0, 'No valid token and no OAuth2 credentials');\n }\n await this.authenticate();\n }\n\n /**\n * Make an authenticated API request\n * @param {string} method - HTTP method\n * @param {string} path - API path (e.g. /display)\n * @param {Object} [params] - Query params (GET) or body params (POST/PUT)\n * @returns {Promise<any>} Response data\n */\n async request(method, path, params = {}) {\n await this.ensureToken();\n\n const url = new URL(`${this.baseUrl}/api${path}`);\n const options = {\n method,\n headers: {\n 'Authorization': `Bearer ${this.accessToken}`\n }\n };\n\n if (method === 'GET') {\n for (const [key, value] of Object.entries(params)) {\n if (value !== undefined && value !== null) {\n url.searchParams.set(key, String(value));\n }\n }\n } else {\n options.headers['Content-Type'] = 'application/x-www-form-urlencoded';\n options.body = new URLSearchParams(params);\n }\n\n const response = await fetch(url, options);\n\n if (!response.ok) {\n const text = await response.text();\n let errorMsg;\n try {\n const errorData = JSON.parse(text);\n errorMsg = errorData.error?.message || errorData.message || text;\n } catch (_) {\n errorMsg = text;\n }\n throw new CmsApiError(method, path, response.status, errorMsg);\n }\n\n // Some endpoints return empty body (204)\n const contentType = response.headers.get('Content-Type') || '';\n if (contentType.includes('application/json')) {\n return response.json();\n }\n return null;\n }\n\n // ── Convenience methods ────────────────────────────────────────\n\n /** GET request (path relative to /api/) */\n get(path, params) { return this.request('GET', path, params); }\n /** POST request (path relative to /api/) */\n post(path, body) { return this.request('POST', path, body); }\n /** PUT request (path relative to /api/) */\n put(path, body) { return this.request('PUT', path, body); }\n /** DELETE request (path relative to /api/) */\n del(path) { return this.request('DELETE', path); }\n\n // ── Display Management ──────────────────────────────────────────\n\n /**\n * Find a display by hardware key\n * @param {string} hardwareKey\n * @returns {Promise<Object|null>} Display object or null if not found\n */\n async findDisplay(hardwareKey) {\n log.info('Looking up display by hardwareKey:', hardwareKey);\n const data = await this.request('GET', '/display', { hardwareKey });\n\n // API returns array of matching displays\n const displays = Array.isArray(data) ? data : [];\n if (displays.length === 0) {\n log.info('No display found for hardwareKey:', hardwareKey);\n return null;\n }\n\n const display = displays[0];\n log.info(`Found display: ${display.display} (ID: ${display.displayId}, licensed: ${display.licensed})`);\n return display;\n }\n\n /**\n * Authorize (toggle licence) a display\n * @param {number} displayId\n * @returns {Promise<void>}\n */\n async authorizeDisplay(displayId) {\n log.info('Authorizing display:', displayId);\n await this.request('PUT', `/display/authorise/${displayId}`);\n log.info('Display authorized successfully');\n }\n\n /**\n * Edit display properties\n * @param {number} displayId\n * @param {Object} properties - Properties to update (display, description, defaultLayoutId, etc.)\n * @returns {Promise<Object>} Updated display\n */\n async editDisplay(displayId, properties) {\n log.info('Editing display:', displayId, properties);\n return this.request('PUT', `/display/${displayId}`, properties);\n }\n\n /**\n * List all displays (with optional filters)\n * @param {Object} [filters] - Optional filters (displayId, display, macAddress, hardwareKey, clientType)\n * @returns {Promise<Array>} Array of display objects\n */\n async listDisplays(filters = {}) {\n const data = await this.request('GET', '/display', filters);\n return Array.isArray(data) ? data : [];\n }\n\n /**\n * Request screenshot from a display\n * @param {number} displayId\n * @returns {Promise<void>}\n */\n async requestScreenshot(displayId) {\n await this.request('PUT', `/display/requestscreenshot/${displayId}`);\n }\n\n /**\n * Get display status\n * @param {number} displayId\n * @returns {Promise<Object>}\n */\n async getDisplayStatus(displayId) {\n return this.request('GET', `/display/status/${displayId}`);\n }\n\n // ── Multipart Requests (File Uploads) ─────────────────────────────\n\n /**\n * Make an authenticated multipart/form-data request (for file uploads).\n * Do NOT set Content-Type — fetch adds the multipart boundary automatically.\n * @param {string} method - HTTP method (POST/PUT)\n * @param {string} path - API path\n * @param {FormData} formData - Form data with files\n * @returns {Promise<any>} Response data\n */\n async requestMultipart(method, path, formData) {\n await this.ensureToken();\n\n const url = `${this.baseUrl}/api${path}`;\n const response = await fetch(url, {\n method,\n headers: {\n 'Authorization': `Bearer ${this.accessToken}`\n // No Content-Type — fetch sets multipart boundary automatically\n },\n body: formData\n });\n\n if (!response.ok) {\n const text = await response.text();\n let errorMsg;\n try {\n const errorData = JSON.parse(text);\n errorMsg = errorData.error?.message || errorData.message || text;\n } catch (_) {\n errorMsg = text;\n }\n throw new Error(`CMS API ${method} ${path} failed (${response.status}): ${errorMsg}`);\n }\n\n const contentType = response.headers.get('Content-Type') || '';\n if (contentType.includes('application/json')) {\n return response.json();\n }\n return null;\n }\n\n // ── Layout Management ─────────────────────────────────────────────\n\n /**\n * Create a new layout\n * @param {Object} params\n * @param {string} params.name - Layout name\n * @param {number} params.resolutionId - Resolution ID\n * @param {string} [params.description] - Description\n * @returns {Promise<Object>} Created layout\n */\n async createLayout({ name, resolutionId, description }) {\n const params = { name, resolutionId };\n if (description) params.description = description;\n return this.request('POST', '/layout', params);\n }\n\n /**\n * List layouts with optional filters\n * @param {Object} [filters] - Filters (layoutId, layout, userId, retired, etc.)\n * @returns {Promise<Array>}\n */\n async listLayouts(filters = {}) {\n const data = await this.request('GET', '/layout', filters);\n return Array.isArray(data) ? data : [];\n }\n\n /**\n * Get a single layout by ID\n * @param {number} layoutId\n * @returns {Promise<Object>}\n */\n async getLayout(layoutId) {\n return this.request('GET', `/layout/${layoutId}`);\n }\n\n /**\n * Delete a layout\n * @param {number} layoutId\n * @returns {Promise<void>}\n */\n async deleteLayout(layoutId) {\n await this.request('DELETE', `/layout/${layoutId}`);\n }\n\n /**\n * Publish a draft layout (makes it available for scheduling)\n * @param {number} layoutId\n * @returns {Promise<void>}\n */\n async publishLayout(layoutId) {\n await this.request('PUT', `/layout/publish/${layoutId}`, { publishNow: 1 });\n }\n\n /**\n * Checkout a published layout (creates editable draft).\n * NOTE: Not needed for newly created layouts — they already have a draft.\n * Use getDraftLayout() to find the auto-created draft instead.\n * @param {number} layoutId\n * @returns {Promise<Object>} Draft layout\n */\n async checkoutLayout(layoutId) {\n return this.request('PUT', `/layout/checkout/${layoutId}`);\n }\n\n /**\n * Get the draft (editable) layout for a given parent layout.\n * In Xibo v4, POST /layout creates a parent + hidden draft automatically.\n * The draft is the one you edit (add regions, widgets) before publishing.\n * @param {number} parentId - The parent layout ID returned by createLayout()\n * @returns {Promise<Object|null>} Draft layout or null if not found\n */\n async getDraftLayout(parentId) {\n const drafts = await this.listLayouts({ parentId });\n return drafts.length > 0 ? drafts[0] : null;\n }\n\n /**\n * Edit layout background\n * @param {number} layoutId\n * @param {Object} params\n * @param {number} [params.backgroundImageId] - Media ID for background image\n * @param {string} [params.backgroundColor] - Hex color (e.g. '#FF0000')\n * @returns {Promise<Object>}\n */\n async editLayoutBackground(layoutId, params) {\n return this.request('PUT', `/layout/background/${layoutId}`, params);\n }\n\n // ── Region Management ─────────────────────────────────────────────\n\n /**\n * Add a region to a layout\n * @param {number} layoutId - Must be the DRAFT layout ID (not the parent)\n * @param {Object} params - { width, height, top, left }\n * @returns {Promise<Object>} Created region with regionPlaylist (singular object, not array)\n */\n async addRegion(layoutId, params) {\n return this.request('POST', `/region/${layoutId}`, params);\n }\n\n /**\n * Edit a region's properties\n * @param {number} regionId\n * @param {Object} params - { width, height, top, left, zIndex }\n * @returns {Promise<Object>}\n */\n async editRegion(regionId, params) {\n return this.request('PUT', `/region/${regionId}`, params);\n }\n\n /**\n * Delete a region\n * @param {number} regionId\n * @returns {Promise<void>}\n */\n async deleteRegion(regionId) {\n await this.request('DELETE', `/region/${regionId}`);\n }\n\n // ── Widget/Playlist Management ────────────────────────────────────\n\n /**\n * Add a widget to a playlist\n *\n * Xibo CMS v4 uses a two-step process:\n * 1. POST creates the widget shell (only templateId and displayOrder are processed)\n * 2. PUT sets all widget properties (uri, duration, mute, etc.)\n *\n * @param {string} type - Widget type (text, image, video, embedded, clock, etc.)\n * @param {number} playlistId - Target playlist ID (from region.playlists[0].playlistId)\n * @param {Object} [properties] - Widget-specific properties\n * @returns {Promise<Object>} Created widget with properties applied\n */\n async addWidget(type, playlistId, properties = {}) {\n // Step 1: Create the widget (only templateId/displayOrder handled by CMS addWidget)\n const { templateId, displayOrder, ...editProps } = properties;\n const createParams = {};\n if (templateId !== undefined) createParams.templateId = templateId;\n if (displayOrder !== undefined) createParams.displayOrder = displayOrder;\n\n const widget = await this.request('POST', `/playlist/widget/${type}/${playlistId}`, createParams);\n\n // Step 2: Set widget properties via editWidget (CMS processes all module properties here)\n if (Object.keys(editProps).length > 0) {\n // useDuration=1 tells CMS to use our custom duration instead of module default\n if (editProps.duration !== undefined && editProps.useDuration === undefined) {\n editProps.useDuration = 1;\n }\n return this.request('PUT', `/playlist/widget/${widget.widgetId}`, editProps);\n }\n\n return widget;\n }\n\n /**\n * Edit a widget's properties\n * @param {number} widgetId\n * @param {Object} properties - Widget-specific properties to update\n * @returns {Promise<Object>}\n */\n async editWidget(widgetId, properties) {\n return this.request('PUT', `/playlist/widget/${widgetId}`, properties);\n }\n\n /**\n * Delete a widget\n * @param {number} widgetId\n * @returns {Promise<void>}\n */\n async deleteWidget(widgetId) {\n await this.request('DELETE', `/playlist/widget/${widgetId}`);\n }\n\n // ── Media / Library ───────────────────────────────────────────────\n\n /**\n * Upload a media file to the library\n * @param {FormData} formData - Must include 'files' field with the file(s)\n * @returns {Promise<Object>} Upload result with media info\n */\n async uploadMedia(formData) {\n return this.requestMultipart('POST', '/library', formData);\n }\n\n /**\n * List media in the library\n * @param {Object} [filters] - Filters (mediaId, media, type, ownerId, etc.)\n * @returns {Promise<Array>}\n */\n async listMedia(filters = {}) {\n const data = await this.request('GET', '/library', filters);\n return Array.isArray(data) ? data : [];\n }\n\n /**\n * Get a single media item by ID\n * @param {number} mediaId\n * @returns {Promise<Object>}\n */\n async getMedia(mediaId) {\n return this.request('GET', `/library/${mediaId}`);\n }\n\n /**\n * Delete a media item from the library\n * @param {number} mediaId\n * @returns {Promise<void>}\n */\n async deleteMedia(mediaId) {\n await this.request('DELETE', `/library/${mediaId}`);\n }\n\n // ── Campaign Management ───────────────────────────────────────────\n\n /**\n * Create a campaign\n * @param {string} name - Campaign name\n * @returns {Promise<Object>} Created campaign\n */\n async createCampaign(name) {\n return this.request('POST', '/campaign', { name });\n }\n\n /**\n * List campaigns\n * @param {Object} [filters] - Filters (campaignId, name, etc.)\n * @returns {Promise<Array>}\n */\n async listCampaigns(filters = {}) {\n const data = await this.request('GET', '/campaign', filters);\n return Array.isArray(data) ? data : [];\n }\n\n /**\n * Delete a campaign\n * @param {number} campaignId\n * @returns {Promise<void>}\n */\n async deleteCampaign(campaignId) {\n await this.request('DELETE', `/campaign/${campaignId}`);\n }\n\n /**\n * Assign a layout to a campaign\n * @param {number} campaignId\n * @param {number} layoutId\n * @param {number} [displayOrder] - Position in campaign playlist\n * @returns {Promise<void>}\n */\n async assignLayoutToCampaign(campaignId, layoutId, displayOrder) {\n const params = { layoutId };\n if (displayOrder !== undefined) params.displayOrder = displayOrder;\n await this.request('POST', `/campaign/layout/assign/${campaignId}`, params);\n }\n\n // ── Schedule Management ───────────────────────────────────────────\n\n /**\n * Create a schedule event\n * @param {Object} params\n * @param {number} params.eventTypeId - 1=Campaign, 2=Command, 3=Overlay\n * @param {number} params.campaignId - Campaign to schedule\n * @param {Array<number>} params.displayGroupIds - Target display group IDs\n * @param {string} params.fromDt - Start date (ISO 8601)\n * @param {string} params.toDt - End date (ISO 8601)\n * @param {number} [params.isPriority] - 0 or 1\n * @param {number} [params.displayOrder] - Order within schedule\n * @returns {Promise<Object>} Created schedule event\n */\n async createSchedule(params) {\n // displayGroupIds needs to be sent as displayGroupIds[] for the API\n const body = { ...params };\n if (Array.isArray(body.displayGroupIds)) {\n // Xibo API expects repeated keys: displayGroupIds[]=1&displayGroupIds[]=2\n // URLSearchParams handles this when we pass entries manually\n delete body.displayGroupIds;\n }\n\n await this.ensureToken();\n\n const url = `${this.baseUrl}/api/schedule`;\n const urlParams = new URLSearchParams();\n\n for (const [key, value] of Object.entries(body)) {\n if (value !== undefined && value !== null) {\n urlParams.set(key, String(value));\n }\n }\n\n // Append array values as repeated keys\n if (Array.isArray(params.displayGroupIds)) {\n for (const id of params.displayGroupIds) {\n urlParams.append('displayGroupIds[]', String(id));\n }\n }\n\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${this.accessToken}`,\n 'Content-Type': 'application/x-www-form-urlencoded'\n },\n body: urlParams\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`CMS API POST /schedule failed (${response.status}): ${text}`);\n }\n\n const contentType = response.headers.get('Content-Type') || '';\n if (contentType.includes('application/json')) {\n return response.json();\n }\n return null;\n }\n\n /**\n * Delete a schedule event\n * @param {number} eventId\n * @returns {Promise<void>}\n */\n async deleteSchedule(eventId) {\n await this.request('DELETE', `/schedule/${eventId}`);\n }\n\n /**\n * List schedule events\n * @param {Object} [filters] - Filters (displayGroupIds, fromDt, toDt)\n * @returns {Promise<Array>}\n */\n async listSchedules(filters = {}) {\n const data = await this.request('GET', '/schedule/data/events', filters);\n return Array.isArray(data) ? data : (data?.events || []);\n }\n\n // ── Display Group Management ──────────────────────────────────────\n\n /**\n * List display groups\n * @param {Object} [filters] - Filters (displayGroupId, displayGroup)\n * @returns {Promise<Array>}\n */\n async listDisplayGroups(filters = {}) {\n const data = await this.request('GET', '/displaygroup', filters);\n return Array.isArray(data) ? data : [];\n }\n\n /**\n * Create a display group\n * @param {string} name - Display group name\n * @param {string} [description]\n * @returns {Promise<Object>} Created display group\n */\n async createDisplayGroup(name, description) {\n const params = { displayGroup: name };\n if (description) params.description = description;\n return this.request('POST', '/displaygroup', params);\n }\n\n /**\n * Delete a display group\n * @param {number} displayGroupId\n * @returns {Promise<void>}\n */\n async deleteDisplayGroup(displayGroupId) {\n await this.request('DELETE', `/displaygroup/${displayGroupId}`);\n }\n\n /**\n * Assign a display to a display group\n * @param {number} displayGroupId\n * @param {number} displayId\n * @returns {Promise<void>}\n */\n async assignDisplayToGroup(displayGroupId, displayId) {\n await this.ensureToken();\n\n const url = `${this.baseUrl}/api/displaygroup/${displayGroupId}/display/assign`;\n const urlParams = new URLSearchParams();\n urlParams.append('displayId[]', String(displayId));\n\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${this.accessToken}`,\n 'Content-Type': 'application/x-www-form-urlencoded'\n },\n body: urlParams\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`CMS API assign display to group failed (${response.status}): ${text}`);\n }\n }\n\n /**\n * Unassign a display from a display group\n * @param {number} displayGroupId\n * @param {number} displayId\n * @returns {Promise<void>}\n */\n async unassignDisplayFromGroup(displayGroupId, displayId) {\n await this.ensureToken();\n\n const url = `${this.baseUrl}/api/displaygroup/${displayGroupId}/display/unassign`;\n const urlParams = new URLSearchParams();\n urlParams.append('displayId[]', String(displayId));\n\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${this.accessToken}`,\n 'Content-Type': 'application/x-www-form-urlencoded'\n },\n body: urlParams\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`CMS API unassign display from group failed (${response.status}): ${text}`);\n }\n }\n\n // ── Resolution Management ─────────────────────────────────────────\n\n /**\n * List available resolutions\n * @returns {Promise<Array>}\n */\n async listResolutions() {\n const data = await this.request('GET', '/resolution');\n return Array.isArray(data) ? data : [];\n }\n\n // ── Template Management ──────────────────────────────────────────\n\n /**\n * List available layout templates\n * @param {Object} [filters] - Filters (layout, tags, etc.)\n * @returns {Promise<Array>}\n */\n async listTemplates(filters = {}) {\n const data = await this.request('GET', '/template', filters);\n return Array.isArray(data) ? data : [];\n }\n\n // ── Playlist Management ──────────────────────────────────────────\n\n /**\n * Assign media library items to a playlist (for file-based widgets: audio, PDF, video)\n * @param {number} playlistId\n * @param {number[]} mediaIds - Array of media IDs to assign\n * @returns {Promise<Object>}\n */\n async assignMediaToPlaylist(playlistId, mediaIds) {\n const ids = Array.isArray(mediaIds) ? mediaIds : [mediaIds];\n // Xibo API expects media[] repeated keys\n await this.ensureToken();\n const url = `${this.baseUrl}/api/playlist/library/assign/${playlistId}`;\n const urlParams = new URLSearchParams();\n for (const id of ids) {\n urlParams.append('media[]', String(id));\n }\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${this.accessToken}`,\n 'Content-Type': 'application/x-www-form-urlencoded'\n },\n body: urlParams\n });\n if (!response.ok) {\n const text = await response.text();\n throw new CmsApiError('POST', `/playlist/library/assign/${playlistId}`, response.status, text);\n }\n const contentType = response.headers.get('Content-Type') || '';\n return contentType.includes('application/json') ? response.json() : null;\n }\n\n // ── Layout Edit ───────────────────────────────────────────────────\n\n /**\n * Edit layout properties\n * @param {number} layoutId\n * @param {Object} params - Properties to update\n * @returns {Promise<Object>}\n */\n async editLayout(layoutId, params) {\n return this.request('PUT', `/layout/${layoutId}`, params);\n }\n}\n\n/**\n * Structured error for CMS API failures\n */\nexport class CmsApiError extends Error {\n constructor(method, path, status, detail) {\n super(`CMS API ${method} ${path} → ${status}: ${detail}`);\n this.name = 'CmsApiError';\n this.method = method;\n this.path = path;\n this.status = status;\n this.detail = detail;\n }\n}\n"],"names":["LOG_LEVELS","logSinks","Logger","name","level","globalConfig","args","_dispatchToSinks","key","hasLocalOverride","urlLevel","storageLevel","createLogger","setLogLevel","getLogLevel","isDebug","applyCmsLogLevel","cmsLevel","mapped","mapCmsLogLevel","fn","registerLogSink","unregisterLogSink","idx","STORAGE_KEY","HW_DB_NAME","HW_DB_VERSION","loadFromEnv","env","define_process_env_default","envConfig","v","Config","json","config","e","newConfig","req","db","tx","resolve","reject","store","backedUpKey","hardwareKey","canvas","ctx","c","r","str","hash","i","result","round","roundHash","val","log","fetchWithRetry","url","options","retryOptions","maxRetries","baseDelayMs","maxDelayMs","lastError","lastResponse","attempt","response","error","jitter","CmsApiClient","baseUrl","clientId","clientSecret","apiToken","text","data","CmsApiError","method","path","params","_a","value","errorMsg","errorData","body","displays","display","displayId","properties","filters","formData","resolutionId","description","layoutId","parentId","drafts","regionId","type","playlistId","templateId","displayOrder","editProps","createParams","widget","widgetId","mediaId","campaignId","urlParams","id","eventId","displayGroupId","mediaIds","ids","status","detail"],"mappings":"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"}
|