@xiboplayer/pwa 0.1.0

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.
Files changed (34) hide show
  1. package/README.md +60 -0
  2. package/dist/assets/cache-proxy-Cx4Z8XMC.js +2 -0
  3. package/dist/assets/cache-proxy-Cx4Z8XMC.js.map +1 -0
  4. package/dist/assets/cms-api-kzy_Sw-u.js +2 -0
  5. package/dist/assets/cms-api-kzy_Sw-u.js.map +1 -0
  6. package/dist/assets/html2canvas.esm-CBrSDip1.js +23 -0
  7. package/dist/assets/html2canvas.esm-CBrSDip1.js.map +1 -0
  8. package/dist/assets/index-BEhNaWZ4.js +2 -0
  9. package/dist/assets/index-BEhNaWZ4.js.map +1 -0
  10. package/dist/assets/index-BPNsrSEv.js +2 -0
  11. package/dist/assets/index-BPNsrSEv.js.map +1 -0
  12. package/dist/assets/index-BY2j60YZ.js +2 -0
  13. package/dist/assets/index-BY2j60YZ.js.map +1 -0
  14. package/dist/assets/index-CTmjUTVM.js +8 -0
  15. package/dist/assets/index-CTmjUTVM.js.map +1 -0
  16. package/dist/assets/index-_q2HbdAU.js +2 -0
  17. package/dist/assets/index-_q2HbdAU.js.map +1 -0
  18. package/dist/assets/index-_uzldOpz.js +16 -0
  19. package/dist/assets/index-_uzldOpz.js.map +1 -0
  20. package/dist/assets/main-C4ABDfkq.js +27 -0
  21. package/dist/assets/main-C4ABDfkq.js.map +1 -0
  22. package/dist/assets/modulepreload-polyfill-B5Qt9EMX.js +2 -0
  23. package/dist/assets/modulepreload-polyfill-B5Qt9EMX.js.map +1 -0
  24. package/dist/assets/pdf-BnPRJEQ6.js +13 -0
  25. package/dist/assets/pdf-BnPRJEQ6.js.map +1 -0
  26. package/dist/assets/setup-rqZh5qYs.js +2 -0
  27. package/dist/assets/setup-rqZh5qYs.js.map +1 -0
  28. package/dist/index.html +130 -0
  29. package/dist/setup.html +371 -0
  30. package/dist/sw-pwa.js +2 -0
  31. package/dist/sw-pwa.js.map +1 -0
  32. package/dist/sw-utils.js +210 -0
  33. package/dist/sw.test.js +271 -0
  34. package/package.json +39 -0
package/README.md ADDED
@@ -0,0 +1,60 @@
1
+ # Xibo Player PWA
2
+
3
+ Lightweight PWA Xibo digital signage player built on the `@xiboplayer` SDK.
4
+
5
+ ## CMS Communication
6
+
7
+ - **REST API** (primary) — used when the CMS exposes the REST endpoint
8
+ - **XMDS SOAP** (fallback) — the standard Xibo player protocol
9
+
10
+ ## Features
11
+
12
+ - Offline-first with Service Worker caching and parallel chunk downloads
13
+ - XLF layout rendering via RendererLite
14
+ - Campaign scheduling with dayparting, interrupts, and overlays
15
+ - Real-time CMS commands via XMR WebSocket
16
+ - Proof of play tracking and stats reporting
17
+ - Configurable log levels (`DEBUG`, `INFO`, `WARNING`, `ERROR`, `NONE`)
18
+
19
+ ## Installation
20
+
21
+ The PWA must be served from the same origin as the Xibo CMS (e.g. `https://your-cms.example.com/player/pwa/`) to avoid CORS restrictions when communicating with the XMDS and REST APIs.
22
+
23
+ Deploy the built `dist/` directory to a path under the CMS web server.
24
+
25
+ ## Development
26
+
27
+ ### Prerequisites
28
+
29
+ - Node.js 20+
30
+ - pnpm 10+
31
+
32
+ ### Setup
33
+
34
+ ```bash
35
+ pnpm install
36
+ ```
37
+
38
+ ### Build
39
+
40
+ ```bash
41
+ pnpm run build
42
+ ```
43
+
44
+ ### Dev server
45
+
46
+ ```bash
47
+ pnpm run dev
48
+ ```
49
+
50
+ ## Testing
51
+
52
+ Playwright E2E tests are in `playwright-tests/`. These are manual QA scripts meant to be run against a live CMS with layouts already scheduled — they are not suitable for CI.
53
+
54
+ ```bash
55
+ PWA_URL=https://your-cms.example.com/player/pwa/ npx playwright test
56
+ ```
57
+
58
+ ## License
59
+
60
+ Apache-2.0
@@ -0,0 +1,2 @@
1
+ import{a as H,c as O,C as K,d as q,L as G,b as V,f as Z,g as Y,i as Q,m as X,r as J,s as ee,u as te}from"./cms-api-kzy_Sw-u.js";class L{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()}}const le=Object.freeze(Object.defineProperty({__proto__:null,CmsApiClient:K,CmsApiError:q,EventEmitter:L,LOG_LEVELS:G,applyCmsLogLevel:V,config:H,createLogger:O,fetchWithRetry:Z,getLogLevel:Y,isDebug:Q,mapCmsLogLevel:X,registerLogSink:J,setLogLevel:ee,unregisterLogSink:te},Symbol.toStringTag,{value:"Module"}));function re(E){return E&&E.__esModule&&Object.prototype.hasOwnProperty.call(E,"default")?E.default:E}var N={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,_;return t!==s&&(r=i(t,e)),n>r?new ArrayBuffer(0):(p=r-n,b=new ArrayBuffer(p),S=new Uint8Array(b),_=new Uint8Array(this,n,p),S.set(_),b)}}();function m(i){return/[\u0080-\uFFFF]/.test(i)&&(i=unescape(encodeURIComponent(i))),i}function v(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 R(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(m(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(m(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=R(i.buff),i},d.ArrayBuffer.prototype.setState=function(i){return i.buff=v(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})})(N);var ne=N.exports;const oe=re(ne),B="xibo-media-v1",ae="xibo-player",se=1,x="files",z=4,k=typeof window<"u"&&window.location.pathname.replace(/\/[^/]*$/,"").replace(/\/$/,"")||"/player/pwa";class ie{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(H.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(ae,se);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 m=parseInt(C.headers.get("Content-Length")||"0"),v=m>100*1024*1024;console.log(`[Cache] File size: ${(m/1024/1024).toFixed(1)} MB ${v?"(large file)":""}`);const R=c==="media"?this.extractFilename(l):s;let A,y;if(v){console.log(`[Cache] Large file detected (${(m/1024/1024).toFixed(1)} MB), caching in background`),this.downloadLargeFileInBackground($,g,m,R,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:m,cachedAt:Date.now(),isBackgroundDownload:!0};return await this.saveFile(o),console.log(`[Cache] ${c}/${s} downloading in background (${m} bytes)`),o}else{this.notifyDownloadProgress(R,0,m);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=oe.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(R,y,m,!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 m="<style>img,video{object-position:center center}</style>";g.includes("</head>")?g=g.replace("</head>",m+"</head>"):g.includes("</HEAD>")&&(g=g.replace("</HEAD>",m+"</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 v=new URL(h,window.location.origin),R=new Response(g,{headers:{"Content-Type":"text/html; charset=utf-8","Access-Control-Allow-Origin":"*"}});if(await u.put(v,R),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,(_,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:_,originalUrl:P})=>{const W=`${k}/cache/static/${encodeURIComponent(_)}`;if(!await y.match(W))try{const D=await fetch(P);if(!D.ok){console.warn(`[Cache] Failed to fetch font: ${_} (HTTP ${D.status})`);return}const F=await D.blob(),j=_.split(".").pop().toLowerCase(),M={otf:"font/otf",ttf:"font/ttf",woff:"font/woff",woff2:"font/woff2",eot:"application/vnd.ms-fontobject",svg:"image/svg+xml"}[j]||"application/octet-stream";await y.put(W,new Response(F,{headers:{"Content-Type":M}})),console.log(`[Cache] Cached font: ${_} (${M}, ${F.size} bytes)`)}catch(D){console.warn(`[Cache] Failed to cache font: ${_}`,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 m;let C=0;console.log(`[Cache] Background download started: ${l}`),this.notifyDownloadProgress(l,0,c);try{const v=[];for(let r=0;r<c;r+=52428800){const p=Math.min(r+52428800-1,c-1);v.push({start:r,end:p,index:v.length})}console.log(`[Cache] Downloading ${v.length} chunks in parallel (${z} concurrent)`);const R=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();R.set(r.index,S),C+=S.size;const _=(C/c*100).toFixed(1);return console.log(`[Cache] Chunk ${r.index}/${v.length-1} complete (${_}%)`),this.notifyDownloadProgress(l,C,c),S}catch(b){throw console.error(`[Cache] Chunk ${r.index} failed:`,b),b}},d=async()=>{for(;A<v.length;){const r=v[A++];await y(r)}},i=[];for(let r=0;r<z;r++)i.push(d());await Promise.all(i);const o=[];for(let r=0;r<v.length;r++)o.push(R.get(r));const t=new Blob(o),e=((m=o[0])==null?void 0:m.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(v){console.error(`[Cache] Background download failed for ${l}:`,v),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 he=new ie,f=O("CacheProxy"),T=typeof window<"u"&&window.location.pathname.replace(/\/[^/]*$/,"").replace(/\/$/,"")||"/player/pwa";class I extends L{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=`${T}/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=`${T}/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");return new Promise((s,c)=>{const l=new MessageChannel;l.port1.onmessage=h=>{const{success:u,error:w,enqueuedCount:g,activeCount:$,queuedCount:C}=h.data;u?(f.info("Download request acknowledged:",g,"files"),f.info("Queue state:",$,"active,",C,"queued"),s()):c(new Error(w||"Service Worker download failed"))},this.controller.postMessage({type:"DOWNLOAD_FILES",data:{files:a}},[l.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){const c=`${T}/cache/${a}/${s}`;try{return(await fetch(c,{method:"HEAD"})).ok}catch{return!1}}}class de extends L{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 I,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 I,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){if(!this.backend)throw new Error("CacheProxy not initialized");return await this.backend.isCached(a,s)}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{de as C,L as E,ie as a,he as c,le as i};
2
+ //# sourceMappingURL=cache-proxy-Cx4Z8XMC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache-proxy-Cx4Z8XMC.js","sources":["../../node_modules/.pnpm/@xiboplayer+utils@0.1.0/node_modules/@xiboplayer/utils/src/event-emitter.js","../../node_modules/.pnpm/spark-md5@3.0.2/node_modules/spark-md5/spark-md5.js","../../node_modules/.pnpm/@xiboplayer+cache@0.1.0/node_modules/@xiboplayer/cache/src/cache.js","../../node_modules/.pnpm/@xiboplayer+cache@0.1.0/node_modules/@xiboplayer/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 {Array} files - Array of { id, type, path, md5 }\n * @returns {Promise<void>}\n */\n async requestDownload(files) {\n if (!this.controller) {\n throw new Error('Service Worker not available');\n }\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: { files }\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\n * @param {string} type - 'media', 'layout', 'widget'\n * @param {string} id - File ID\n * @returns {Promise<boolean>}\n */\n async isCached(type, id) {\n const cacheUrl = `${BASE}/cache/${type}/${id}`;\n\n try {\n const response = await fetch(cacheUrl, { method: 'HEAD' });\n return response.ok;\n } catch (error) {\n return false;\n }\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\n * @param {string} type - 'media', 'layout', 'widget'\n * @param {string} id - File ID\n * @returns {Promise<boolean>}\n */\n async isCached(type, id) {\n if (!this.backend) {\n throw new Error('CacheProxy not initialized');\n }\n return await this.backend.isCached(type, id);\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","files","messageChannel","success","enqueuedCount","activeCount","queuedCount","fileType","fileId","CacheProxy","_b","swReady","timeout","_","channel","deleted","mediaIds","controller","warmed"],"mappings":"gIAKO,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,icC5EC,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,wCCvuBKM,EAAa,gBACbC,GAAU,cACVC,GAAa,EACbC,EAAc,QACdC,EAAoB,EAGpBC,EAAQ,OAAO,OAAW,KAC5B,OAAO,SAAS,SAAS,QAAQ,WAAY,EAAE,EAAE,QAAQ,MAAO,EAAE,GAAK,cAGpE,MAAMC,EAAa,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,GAASC,EAAU,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,GAAS,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,GAAe,IAAIxF,GClsB1ByF,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,EAAO,CAC3B,GAAI,CAAC,KAAK,WACR,MAAM,IAAI,MAAM,8BAA8B,EAGhD,OAAO,IAAI,QAAQ,CAACxF,EAASC,IAAW,CACtC,MAAMwF,EAAiB,IAAI,eAE3BA,EAAe,MAAM,UAAapK,GAAU,CAC1C,KAAM,CAAE,QAAAqK,EAAS,MAAA7B,EAAO,cAAA8B,EAAe,YAAAC,EAAa,YAAAC,CAAW,EAAKxK,EAAM,KACtEqK,GACFN,EAAI,KAAK,iCAAkCO,EAAe,OAAO,EACjEP,EAAI,KAAK,eAAgBQ,EAAa,UAAWC,EAAa,QAAQ,EACtE7F,EAAO,GAEPC,EAAO,IAAI,MAAM4D,GAAS,gCAAgC,CAAC,CAE/D,EAEA,KAAK,WAAW,YACd,CACE,KAAM,iBACN,KAAM,CAAE,MAAA2B,CAAK,CACvB,EACQ,CAACC,EAAe,KAAK,CAC7B,CACI,CAAC,CACH,CAQA,MAAM,mBAAmBK,EAAUC,EAAQ,CACzC,GAAK,KAAK,WAEV,OAAO,IAAI,QAAS/F,GAAY,CAC9B,MAAMyF,EAAiB,IAAI,eAC3BA,EAAe,MAAM,UAAapK,GAAU2E,EAAQ3E,EAAM,IAAI,EAC9D,KAAK,WAAW,YACd,CAAE,KAAM,sBAAuB,KAAM,CAAE,SAAAyK,EAAU,OAAAC,CAAM,CAAE,EACzD,CAACN,EAAe,KAAK,CAC7B,CACI,CAAC,CACH,CAQA,MAAM,SAASlF,EAAMH,EAAI,CACvB,MAAMoC,EAAW,GAAG9C,CAAI,UAAUa,CAAI,IAAIH,CAAE,GAE5C,GAAI,CAEF,OADiB,MAAM,MAAMoC,EAAU,CAAE,OAAQ,OAAQ,GACzC,EAClB,MAAgB,CACd,MAAO,EACT,CACF,CACF,CAOO,MAAMwD,WAAmB5K,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,aAAaa,EAAAV,EAAa,UAAb,YAAAU,EAAsB,KAAK,GAIpDb,EAAI,KAAK,uDAAuD,EAGhE,MAAMc,EAAU,UAAU,cAAc,MAClCC,EAAU,IAAI,QAAQ,CAACC,EAAGnG,IAC9B,WAAW,IAAMA,EAAO,IAAI,MAAM,wCAAwC,CAAC,EAAG,GAAK,CACzF,EAEI,GAAI,CACF,MAAM,QAAQ,KAAK,CAACiG,EAASC,CAAO,CAAC,EACrCf,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,gBAAgBoF,EAAO,CAC3B,GAAI,CAAC,KAAK,QACR,MAAM,IAAI,MAAM,4BAA4B,EAE9C,OAAO,MAAM,KAAK,QAAQ,gBAAgBA,CAAK,CACjD,CAOA,MAAM,mBAAmBM,EAAUC,EAAQ,OACzC,IAAKpF,EAAA,KAAK,UAAL,MAAAA,EAAc,mBACnB,OAAO,MAAM,KAAK,QAAQ,mBAAmBmF,EAAUC,CAAM,CAC/D,CAQA,MAAM,SAASxF,EAAMH,EAAI,CACvB,GAAI,CAAC,KAAK,QACR,MAAM,IAAI,MAAM,4BAA4B,EAE9C,OAAO,MAAM,KAAK,QAAQ,SAASG,EAAMH,CAAE,CAC7C,CAMA,gBAAiB,CACf,OAAO,KAAK,WACd,CAMA,sBAAuB,CACrB,OAAO,KAAK,cAAgB,gBAC9B,CAOA,MAAM,YAAYoF,EAAO,CACvB,GAAI,CAAC,KAAK,QACR,MAAM,IAAI,MAAM,4BAA4B,EAG9C,OAAO,IAAI,QAAQ,CAACxF,EAASC,IAAW,CACtC,MAAMoG,EAAU,IAAI,eAEpBA,EAAQ,MAAM,UAAahL,GAAU,CACnC,KAAM,CAAE,QAAAqK,EAAS,MAAA7B,EAAO,QAAAyC,EAAS,MAAArB,CAAK,EAAK5J,EAAM,KAC7CqK,EACF1F,EAAQ,CAAE,QAAAsG,EAAS,MAAArB,EAAO,EAE1BhF,EAAO,IAAI,MAAM4D,GAAS,eAAe,CAAC,CAE9C,EAEA,UAAU,cAAc,WAAW,YACjC,CAAE,KAAM,eAAgB,KAAM,CAAE,MAAA2B,CAAK,CAAE,EACvC,CAACa,EAAQ,KAAK,CACtB,EAEM,WAAW,IAAMrG,EAAQ,CAAE,QAAS,EAAG,MAAOwF,EAAM,OAAQ,EAAG,GAAI,CACrE,CAAC,CACH,CAQA,MAAM,mBAAmBe,EAAU,OACjC,GAAI,CAACA,GAAYA,EAAS,SAAW,EAAG,MAAO,CAAE,OAAQ,EAAG,MAAO,CAAC,EAEpE,MAAMC,GAAa7F,EAAA,UAAU,gBAAV,YAAAA,EAAyB,WAC5C,OAAK6F,EAEE,IAAI,QAASxG,GAAY,CAC9B,MAAMqG,EAAU,IAAI,eAEpBA,EAAQ,MAAM,UAAahL,GAAU,CACnC,KAAM,CAAE,QAAAqK,EAAS,OAAAe,EAAQ,MAAAxB,CAAK,EAAK5J,EAAM,KACzC2E,EAAQ0F,EAAU,CAAE,OAAAe,EAAQ,MAAAxB,CAAK,EAAK,CAAE,OAAQ,EAAG,MAAOsB,EAAS,MAAM,CAAE,CAC7E,EAEAC,EAAW,YACT,CAAE,KAAM,uBAAwB,KAAM,CAAE,SAAAD,CAAQ,CAAE,EAClD,CAACF,EAAQ,KAAK,CACtB,EAGM,WAAW,IAAMrG,EAAQ,CAAE,OAAQ,EAAG,MAAOuG,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,QAASvG,GAAY,CAC9B,MAAMqG,EAAU,IAAI,eAEpBA,EAAQ,MAAM,UAAahL,GAAU,CACnC,KAAM,CAAE,QAAAqK,EAAS,SAAAd,CAAQ,EAAKvJ,EAAM,KACpC2E,EAAQ0F,EAAUd,EAAW,EAAE,CACjC,EAEA,UAAU,cAAc,WAAW,YACjC,CAAE,KAAM,uBAAuB,EAC/B,CAACyB,EAAQ,KAAK,CACtB,EAGM,WAAW,IAAMrG,EAAQ,CAAA,CAAE,EAAG,GAAI,CACpC,CAAC,CACH,CACF","x_google_ignoreList":[0,1,2,3]}
@@ -0,0 +1,2 @@
1
+ const d={DEBUG:0,INFO:1,WARNING:2,ERROR:3,NONE:4},f=[];class ${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.INFO,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):window.location.hostname==="localhost"||window.location.hostname==="127.0.0.1"?h.setGlobalLevel("DEBUG"):h.setGlobalLevel("INFO")}function b(i,e=null){return new $(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 D(i){if(T||!i)return!1;const e=A(i);return h.setGlobalLevel(e),!0}function A(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 O(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 N=new C,k=b("FetchRetry");async function U(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 I{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{I as C,d as L,N as a,D as b,b as c,m as d,U as f,P as g,G as i,A as m,O as r,v as s,K as u};
2
+ //# sourceMappingURL=cms-api-kzy_Sw-u.js.map