@xiboplayer/cache 0.5.5 → 0.5.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiboplayer/cache",
3
- "version": "0.5.5",
3
+ "version": "0.5.7",
4
4
  "description": "Offline caching and download management with parallel chunk downloads",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -12,7 +12,7 @@
12
12
  },
13
13
  "dependencies": {
14
14
  "spark-md5": "^3.0.2",
15
- "@xiboplayer/utils": "0.5.5"
15
+ "@xiboplayer/utils": "0.5.7"
16
16
  },
17
17
  "devDependencies": {
18
18
  "vitest": "^2.0.0",
@@ -34,6 +34,12 @@ const DEFAULT_MAX_CHUNKS_PER_FILE = 3; // Max parallel chunk downloads per file
34
34
  const CHUNK_THRESHOLD = 100 * 1024 * 1024; // Files > 100MB get chunked
35
35
  const MAX_RETRIES = 3;
36
36
  const RETRY_DELAY_MS = 500; // Fast: 500ms, 1s, 1.5s → total ~3s
37
+
38
+ // getData (widget data) retry config — CMS "cache not ready" (HTTP 500) resolves
39
+ // when the XTR task runs (30-120s). Use longer backoff to ride it out.
40
+ const GETDATA_MAX_RETRIES = 4;
41
+ const GETDATA_RETRY_DELAYS = [15_000, 30_000, 60_000, 120_000]; // 15s, 30s, 60s, 120s
42
+ const GETDATA_REENQUEUE_DELAY_MS = 60_000; // Re-add to queue after 60s if all retries fail
37
43
  const URGENT_CONCURRENCY = 2; // Slots when urgent chunk is active (bandwidth focus)
38
44
  const FETCH_TIMEOUT_MS = 600_000; // 10 minutes — 100MB chunk at ~2 Mbps
39
45
  const HEAD_TIMEOUT_MS = 15_000; // 15 seconds for HEAD requests
@@ -127,6 +133,8 @@ export class DownloadTask {
127
133
  this.blob = null;
128
134
  this._parentFile = null;
129
135
  this._priority = PRIORITY.normal;
136
+ // Widget data (getData) uses longer retry backoff — CMS "cache not ready" is transient
137
+ this.isGetData = fileInfo.isGetData || false;
130
138
  }
131
139
 
132
140
  getUrl() {
@@ -144,7 +152,9 @@ export class DownloadTask {
144
152
  headers['Range'] = `bytes=${this.rangeStart}-${this.rangeEnd}`;
145
153
  }
146
154
 
147
- for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
155
+ const maxRetries = this.isGetData ? GETDATA_MAX_RETRIES : MAX_RETRIES;
156
+
157
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
148
158
  const ac = new AbortController();
149
159
  const timer = setTimeout(() => ac.abort(), FETCH_TIMEOUT_MS);
150
160
  try {
@@ -163,10 +173,12 @@ export class DownloadTask {
163
173
 
164
174
  } catch (error) {
165
175
  const msg = ac.signal.aborted ? `Timeout after ${FETCH_TIMEOUT_MS / 1000}s` : error.message;
166
- if (attempt < MAX_RETRIES) {
167
- const delay = RETRY_DELAY_MS * attempt;
176
+ if (attempt < maxRetries) {
177
+ const delay = this.isGetData
178
+ ? GETDATA_RETRY_DELAYS[attempt - 1]
179
+ : RETRY_DELAY_MS * attempt;
168
180
  const chunkLabel = this.chunkIndex != null ? ` chunk ${this.chunkIndex}` : '';
169
- log.warn(`[DownloadTask] ${this.fileInfo.type}/${this.fileInfo.id}${chunkLabel} attempt ${attempt}/${MAX_RETRIES} failed: ${msg}. Retrying in ${delay / 1000}s...`);
181
+ log.warn(`[DownloadTask] ${this.fileInfo.type}/${this.fileInfo.id}${chunkLabel} attempt ${attempt}/${maxRetries} failed: ${msg}. Retrying in ${delay / 1000}s...`);
170
182
  await new Promise(resolve => setTimeout(resolve, delay));
171
183
  } else {
172
184
  this.state = 'failed';
@@ -859,6 +871,22 @@ export class DownloadQueue {
859
871
  this.running--;
860
872
  task._parentFile._runningCount--;
861
873
  this._activeTasks = this._activeTasks.filter(t => t !== task);
874
+
875
+ // getData (widget data): defer re-enqueue instead of permanent failure.
876
+ // CMS "cache not ready" resolves when the XTR task runs (30-120s).
877
+ if (task.isGetData) {
878
+ log.warn(`[DownloadQueue] getData ${key} failed all retries, scheduling re-enqueue in ${GETDATA_REENQUEUE_DELAY_MS / 1000}s`);
879
+ setTimeout(() => {
880
+ task.state = 'pending';
881
+ task._parentFile.state = 'downloading';
882
+ this.queue.push(task);
883
+ log.info(`[DownloadQueue] getData ${key} re-enqueued for retry`);
884
+ this.processQueue();
885
+ }, GETDATA_REENQUEUE_DELAY_MS);
886
+ this.processQueue();
887
+ return;
888
+ }
889
+
862
890
  this.processQueue();
863
891
  task._parentFile.onTaskFailed(task, err);
864
892
  });
@@ -13,6 +13,7 @@
13
13
  */
14
14
 
15
15
  import { createLogger } from '@xiboplayer/utils';
16
+ import { rewriteUrlForProxy } from './download-manager.js';
16
17
 
17
18
  const log = createLogger('Cache');
18
19
  const CACHE_NAME = 'xibo-media-v1';
@@ -110,7 +111,7 @@ export async function cacheWidgetHtml(layoutId, regionId, mediaId, html) {
110
111
  if (existing) return; // Already cached
111
112
 
112
113
  try {
113
- const resp = await fetch(originalUrl);
114
+ const resp = await fetch(rewriteUrlForProxy(originalUrl));
114
115
  if (!resp.ok) {
115
116
  log.warn(`Failed to fetch static resource: ${filename} (HTTP ${resp.status})`);
116
117
  return;
@@ -149,7 +150,7 @@ export async function cacheWidgetHtml(layoutId, regionId, mediaId, html) {
149
150
  if (existingFont) return; // Already cached (by SW or previous widget)
150
151
 
151
152
  try {
152
- const fontResp = await fetch(fontUrl);
153
+ const fontResp = await fetch(rewriteUrlForProxy(fontUrl));
153
154
  if (!fontResp.ok) {
154
155
  log.warn(`Failed to fetch font: ${fontFile} (HTTP ${fontResp.status})`);
155
156
  return;